/**
 * @file voice_msg.c
 *
 */

/*********************
*      INCLUDES
*********************/
#include "lv_watch.h"

#if USE_LV_WATCH_VOICE_MSG != 0

/*********************
*      DEFINES
*********************/
#define VOICE_MSG_MAX_RECORD_DURATION 16 /*in seconds*/
#define VOICE_MSG_MAX_RECORD_SIZE VOICE_MSG_MAX_RECORD_DURATION * HAL_AMR_BUFSIZE_PER_SECOND + VOICE_MSG_AMR_HEAD_SIZE
#define VOICE_MSG_MAX_CHAT_MSG_NUM APP_ADAPTOR_VOICE_MSG_MAX_CHAT_MSG_NUM
#define VOICE_MSG_FAMILY_GROUP_CHAT_WITH_ADMIN_ONLY 1 /*1:add family group when admin is added,
                                                       *0:add family group when the first standby admin is added,
                                                       *and add friends group when the second others is added*/

/**********************
*      TYPEDEFS
**********************/

/**********************
*  STATIC PROTOTYPES
**********************/
static uint32_t voice_msg_record_buffer_stop_req(void);
static void voice_msg_set_slide_enable(lv_watch_obj_ext_t * ext, bool enable);
static void voice_msg_lcd_wakeup(void);
static bool voice_msg_is_higher_prio_task_present(lv_watch_Activity_Id_t exception);
static void voice_msg_del_interface(lv_watch_Activity_Id_t act_id);
static bool voice_msg_compare_contacts(app_adaptor_voice_msg_t * msg, voice_msg_contact_t * contact);
static char * voice_msg_copy_text(uint16_t text_id);
static bool voice_msg_update_contact_list_from_pb(voice_msg_update_contact_t * update,
        lv_ll_t * contact_list,
        voice_msg_contact_t ** cur_contact);
static bool voice_msg_update_nvm_from_pb(voice_msg_update_contact_t * update);
static void voice_msg_write_nvm(lv_ll_t * contact_list);
static void voice_msg_high_priority_task_end(void);
#if VOICE_MSG_ENABLE_ONE_UNREAD_MSG != 0
static void voice_msg_create_one_unread_msg(app_adaptor_voice_msg_t * info,
        lv_ll_t * contact_list,
        voice_msg_contact_t * contact);
#endif
static void voice_msg_create_multi_unread_msgs(lv_obj_t * obj,
        uint8_t unread_count,
        lv_ll_t * contact_list,
        voice_msg_contact_t * contact);
static void voice_msg_update_multi_unread_msgs(lv_obj_t * obj, uint8_t unread_count);
static nv_watch_voice_msg_t * voice_msg_read_index_nvm(void);
static nv_watch_phonebook_t * voice_msg_read_pb_nvm(void);
static lv_ll_t * voice_msg_read_nvm(void);
static bool voice_msg_update_nvm(app_adaptor_voice_msg_t * msg);
static void voice_msg_update_contact_list(lv_voice_msg_ext_t * ext);
static bool voice_msg_send_voice(lv_voice_msg_chat_ext_t * ext);
lv_obj_t * voice_msg_create_send(lv_obj_t * activity_obj);
static void voice_msg_note_create(lv_obj_t * obj, uint16_t text_id);
static void voice_msg_no_network_or_sim_create(lv_obj_t * obj);
static void voice_msg_free_msgs(app_adaptor_voice_msg_t * msgs);
static void voice_msg_contacts_create(lv_obj_t * obj);
static lv_obj_t * voice_msg_get_obj(lv_watch_Activity_Id_t actId);
static void voice_msg_chat_stop_voice(lv_voice_msg_chat_ext_t * ext);
static void voice_msg_add_new_msg_to_chat(app_adaptor_voice_msg_t * msgs);
static void voice_msg_chat_record_cb(MCI_EVNET_T event, MCI_INFO_T info_type, int32_t value);
static void voice_msg_create_speak(void);
static void voice_msg_prepare_destory(lv_obj_t * activity_obj);
static void voice_msg_no_contacts_create(lv_obj_t * obj);
static void voice_msg_contact_list_create(lv_voice_msg_ext_t * ext, lv_coord_t pos);
static void voice_msg_send_end_anim(lv_anim_t * a);
static lv_obj_t  * voice_msg_create_expression(lv_obj_t * activity_obj);
static void voice_msg_create_enlarged_photo(void * photo);
static void voice_msg_msg_incoming_over_cb(void * para);

/**********************
*  STATIC VARIABLES
**********************/
static app_adaptor_voice_msg_t * msg_buf = NULL; // buffer the msg received during mt call & alarm

/**********************
*      MACROS
**********************/

/**********************
 *   GLOBAL FUNCTIONS
 ***********************/
lv_obj_t * voice_msg_create(lv_obj_t * activity_obj, voice_msg_from_app_t other_app, lv_ll_t * contact_list)
{
    printf("%s\n", __FUNCTION__);

#if VOICE_MSG_TEST != 0
    static bool first = true;
    if(true == first) {
        /* printf("----first %d\n", first); */
        voice_msg_test_init();

        uint32_t length_test = sizeof(nv_watch_voice_msg_t);
        nv_watch_voice_msg_t * nvm_test = (nv_watch_voice_msg_t *)lv_mem_alloc(length_test);
        memset(nvm_test, 0xFF, length_test);
        nvm_test->index[0] = 0;
        nvm_test->index[1] = 2;
        nvm_test->index[2] = 3;
        nvm_test->index[3] = 4;
        nvm_test->index[4] = VOICE_MSG_FAMILY_GROUP;
        nvm_test->index[5] = 5;
        nvm_test->index[6] = 1;
        nvm_test->index[7] = 7;
        nvm_test->index[8] = 8;
        nvm_test->index[9] = 6;
#if VOICE_MSG_FAMILY_GROUP_CHAT_WITH_ADMIN_ONLY != 1
        nvm_test->index[10] = VOICE_MSG_FRIENDS_GROUP;
#endif
        nvm_test->mark[0] = 5;
        if(length_test != UI_NV_Write_Req(NV_SECTION_UI_VOICE_MSG, 0, length_test, (uint8_t *)nvm_test)) {
            printf("write nvm for test in voice_msg_create\n");
        }
        lv_mem_free(nvm_test);
    }
#if VOICE_MSG_APP_SPECIFIC_TEST != 0
    first = false;
#endif
#endif

    if(activity_obj == NULL) {
        lv_watch_activity_ext_t activity_ext;
        memset(&activity_ext, 0, sizeof(lv_watch_activity_ext_t));
        activity_ext.actId = ACT_ID_VOICE_MSG;
        activity_ext.create = NULL; /*voice_msg_create;*/
        activity_ext.prepare_destory = voice_msg_prepare_destory;
        activity_obj = lv_watch_creat_activity_obj(&activity_ext);
        LV_ASSERT_MEM(activity_obj);
    }

    lv_obj_t * obj = lv_watch_obj_create(activity_obj);
    LV_ASSERT_MEM(obj);
    /*Allocate the tab type specific extended data*/
    lv_voice_msg_ext_t * ext = lv_obj_allocate_ext_attr(obj, sizeof(lv_voice_msg_ext_t));
    LV_ASSERT_MEM(ext);
    ext->pressing = false;
    ext->other_app = other_app;
    ext->contact_list = NULL;

    lv_style_init(&ext->style_cont_mark);
    lv_style_set_bg_color(&ext->style_cont_mark, LV_STATE_DEFAULT, LV_COLOR_RED);
    lv_style_set_radius(&ext->style_cont_mark, LV_STATE_DEFAULT, LV_RADIUS_CIRCLE);
    lv_style_set_border_opa(&ext->style_cont_mark, LV_STATE_DEFAULT, LV_OPA_0);

    lv_style_init(&ext->style_label_mark);
    lv_style_copy(&ext->style_label_mark, &lv_watch_font20);
    lv_style_set_text_color(&ext->style_label_mark, LV_STATE_DEFAULT, LV_COLOR_BLACK);

    if(setting_is_flying_mode()) {
        voice_msg_note_create(obj, WATCH_TEXT_ID_PLEASE_TURN_OFF_FLYING_MODE);
        return obj;
    }

    bool is_no_connetion = false;
    if((false == watch_modem_sim_present_check_req())
       || (MMI_MODEM_SIGNAL_BAR_0 == watch_modem_get_signal_bar_req())) {
        is_no_connetion = true;
    }
#if USE_LV_WLAN != 0
    if(is_no_connetion) {
        if(hal_wlan_is_connected_state()) {
            is_no_connetion = false;
        }
    }
#endif

    if(is_no_connetion) {
        voice_msg_no_network_or_sim_create(obj);
        return obj;
    }

    if(contact_list)
        ext->contact_list = contact_list;
    else
        ext->contact_list = voice_msg_read_nvm();

    if(NULL == ext->contact_list->head) {
        /*no contacts*/
        voice_msg_no_contacts_create(obj);
        return obj;
    }

    voice_msg_contacts_create(obj);

    return(obj);
}

void voice_msg_create_event_cb(lv_obj_t * btn, lv_event_t e)
{
    (void)btn;
    if(LV_EVENT_CLICKED == e) {
        lv_watch_png_cache_all_free();
        lv_obj_t * obj = voice_msg_create(NULL, 0, NULL);
        LV_ASSERT_MEM(obj);
    }
}

static void voice_msg_msg_incoming_over_cb(void * para)
{
    Hal_Audio_Manage_End_Ind(AUDIO_CTRL_PRIORITY_5);
}

void app_adaptor_voice_msg_handle_rcv_ind(app_adaptor_voice_msg_t * msg)
{
    if(NULL == msg) return;
    if(NULL == msg->msg) return;
    if(NULL == msg->name) {
        if(WATCH_VOICE_MSG_FAMILY_GROUP_CHAT == msg->chat_type) {
            msg->name = voice_msg_copy_text(WATCH_TEXT_ID_FAMILY_GROUP);
        } else if(WATCH_VOICE_MSG_FRIENDS_GROUP_CHAT == msg->chat_type) {
            msg->name = voice_msg_copy_text(WATCH_TEXT_ID_FRIENDS_GROUP);
        }
    }

    printf("%s: chat type %d, rcv msg from %s\n", __FUNCTION__, msg->chat_type, msg->name);

    lv_voice_msg_ext_t * ext = voice_msg_get_ext(ACT_ID_VOICE_MSG);
    lv_voice_msg_chat_ext_t * chat_ext = voice_msg_get_ext(ACT_ID_VOICE_MSG_CHAT);
    lv_voice_msg_incoming_ext_t * incom_ext = voice_msg_get_ext(ACT_ID_VOICE_MSG_INCOMING);
    lv_ll_t * contact_list = NULL;
    voice_msg_contact_t ** cur_contact = NULL;
    bool chat_contact = false;
    if(chat_ext) {
        contact_list = chat_ext->contact_list;
        cur_contact = &chat_ext->cur_contact;
        if(true == voice_msg_compare_contacts(msg, chat_ext->cur_contact)) {
            voice_msg_add_new_msg_to_chat(msg);
            chat_contact = true;
        }
    } else if(ext) {
        contact_list = ext->contact_list;
    } else if(incom_ext) {
        contact_list = incom_ext->contact_list;
        cur_contact = &incom_ext->cur_contact;
    } else {
        if(AUDIO_CTRL_PRIORITY_5 != Hal_Audio_Manage_Start_Req(AUDIO_CTRL_PRIORITY_5,voice_msg_audio_ctrl_callback)) {
            if(voice_msg_update_nvm(msg)) {
                printf("%s: buff the new message, display after the end of higher priority task\n", __FUNCTION__);
                if(msg_buf)
                    voice_msg_free_msgs(msg_buf);
                msg_buf = msg;
            } else {
                printf("%s: warning, contact not found, discard the new message\n", __FUNCTION__);
                voice_msg_free_msgs(msg);
            }
            return;
        } else if(false == watch_is_ready()) {
            printf("%s: rcv msg during power off, not display new msg interface\n", __FUNCTION__);
            voice_msg_update_nvm(msg);
            voice_msg_free_msgs(msg);
            return;
        } else {
            printf("%s: rcv msg without voice msg interface\n", __FUNCTION__);
            contact_list = voice_msg_read_nvm();
        }
    }

    voice_msg_contact_t * head = _lv_ll_get_head(contact_list);
    voice_msg_contact_t * contact = head;
    bool contact_list_update = false;
    while(contact) {
        if(true == voice_msg_compare_contacts(msg, contact)) {
            if(head != contact) {
                /*need to update contact position in contact list*/
                contact_list_update = true;
#if VOICE_MSG_SORT_CONTACTS_BY_MSG_TIME != 0
                lv_ll_move_before(contact_list, contact, head);
#endif
            } else {
                if(false == chat_contact) {
                    /*need to update mark in contact list*/
                    contact_list_update = true;
                }
            }
            if(false == chat_contact) {
                if(0xFF == contact->mark) contact->mark = 1;
                else contact->mark++;
            }

            break;
        }
        contact = _lv_ll_get_next(contact_list, contact);
    }
    if(NULL == contact) {
#if 0
        /*add a new contact*/
        nv_watch_phonebook_t * nvm_pb = voice_msg_read_pb_nvm();

        for(uint8_t j = 0; j < NV_WATCH_MAX_CONTACT_NUM; j++) {
            if(0 == strcmp(msg->name, nvm_pb->info[j].name)) {
                /*add the contact to the top*/
                contact = lv_ll_ins_head(contact_list);
                memcpy(contact->name, nvm_pb->info[j].name, NV_CONTACTS_MAX_NAME_LEN);
                contact->portrait_id = nvm_pb->info[j].portrait_id;
                contact->index = j;
                contact->mark = 1;
                contact_list_update = true;
                break;
            }
        }
        lv_mem_free(nvm_pb);
#endif
        printf("%s: message contact mismatched!\n", __FUNCTION__);
        if((NULL == chat_ext) && (NULL == ext) && (NULL == incom_ext)) {
            if(contact_list) {
                _lv_ll_clear(contact_list);
                lv_mem_free(contact_list);
            }
        }
        voice_msg_free_msgs(msg);
        return;
    }

    if(cur_contact) {
        *cur_contact = contact;
    }
    if(chat_ext) {
        if((ext) && (contact_list_update)) {
            chat_ext->contact_list_update = true;
        }
    } else if(ext) {
        voice_msg_update_contact_list(ext);
    } else {
        voice_msg_contact_t * temp = _lv_ll_get_head(contact_list);
        uint16_t num_unread_msg = 0;
        while(temp) {
            if(0xFF != temp->mark) {
                num_unread_msg += temp->mark;
            }
            temp = _lv_ll_get_next(contact_list, temp);
        }
        if(NULL == incom_ext) {
#if VOICE_MSG_ENABLE_ONE_UNREAD_MSG != 0
            if(1 == num_unread_msg) {
                voice_msg_create_one_unread_msg(msg, contact_list, contact);
            } else {
                voice_msg_create_multi_unread_msgs(NULL, num_unread_msg, contact_list, contact);
            }
#else
            voice_msg_create_multi_unread_msgs(NULL, num_unread_msg, contact_list, contact);
#endif
        } else {
            lv_obj_t * incom_obj = voice_msg_get_obj(ACT_ID_VOICE_MSG_INCOMING);
#if VOICE_MSG_ENABLE_ONE_UNREAD_MSG != 0
            if(2 == num_unread_msg) {
                voice_msg_create_multi_unread_msgs(incom_obj, num_unread_msg, contact_list, contact);
            } else {
                voice_msg_update_multi_unread_msgs(incom_obj, num_unread_msg);
            }
#else
            voice_msg_update_multi_unread_msgs(incom_obj, num_unread_msg);
#endif
        }
    }

    if((false == chat_contact) && (AUDIO_CTRL_PRIORITY_5 == Hal_Audio_Manage_Start_Req(AUDIO_CTRL_PRIORITY_5,voice_msg_audio_ctrl_callback))) {
        Voice_Msg_Play_Onetime(AUDIO_MSG, 0, query_current_volume(), NULL, NULL);
        voice_msg_lcd_wakeup();
    }

    voice_msg_free_msgs(msg);
}

void app_adaptor_voice_msg_rcv_ind(app_adaptor_voice_msg_t * voice_msg)
{
#if USE_LV_WATCH_MODEM_ADAPTOR
    mmi_msg_app_adaptor_voice_msg_rcv_ind_t * msg;
    msg = (mmi_msg_app_adaptor_voice_msg_rcv_ind_t *)lv_mem_alloc(sizeof(mmi_msg_app_adaptor_voice_msg_rcv_ind_t));
    msg->header.MsgId = MMI_APP_ADAPTOR_VOICE_MSG_RCV_IND;
    msg->msg = voice_msg;

    Hal_Send_Message((TASK_ID)gui_task_id, (void *)msg);
#else /*for PC simulator*/
    app_adaptor_voice_msg_handle_rcv_ind(voice_msg);
#endif
}

void app_adaptor_voice_msg_handle_send_cnf(watch_app_adp_voice_msg_result_t result)
{
    printf("%s\n", __FUNCTION__);

    lv_obj_t * activity_obj = lv_watch_get_activity_obj(ACT_ID_VOICE_MSG_SEND);
    if(NULL == activity_obj) return;

    printf("%s: VOICE_MSG_SEND\n", __FUNCTION__);
    lv_obj_t * obj;
    lv_watch_get_child_obj(activity_obj, lv_watch_obj_signal, &obj);
    if(NULL == obj) return;

    lv_obj_t * img_bkg = lv_obj_get_child_back(obj, NULL);
    if(NULL == img_bkg) return;

    lv_obj_t * img_sending = lv_obj_get_child_back(img_bkg, NULL);
    if(NULL == img_sending) return;
    lv_obj_del(img_sending);

    if(WATCH_VOICE_MSG_RESULT_SUCCESS == result) {
        lv_img_set_src(img_bkg, ICON_SND_VOICE_SUCC);
    } else if(WATCH_VOICE_MSG_RESULT_FAILURE == result) {
        lv_img_set_src(img_bkg, ICON_SND_VOICE_FAILED);
    } else {
        return;
    }

    lv_anim_t a = {};
    lv_anim_init(&a);
    lv_anim_set_var(&a, activity_obj);
    lv_anim_set_ready_cb(&a, (lv_anim_ready_cb_t)voice_msg_send_end_anim);
    lv_anim_set_time(&a, 2000);
    lv_anim_start(&a);
}

void app_adaptor_voice_msg_send_cnf(watch_app_adp_voice_msg_result_t result)
{
#if USE_LV_WATCH_MODEM_ADAPTOR
    mmi_msg_app_adaptor_voice_msg_send_cnf_t * msg;
    msg = (mmi_msg_app_adaptor_voice_msg_send_cnf_t *)lv_mem_alloc(sizeof(mmi_msg_app_adaptor_voice_msg_send_cnf_t));
    msg->header.MsgId = MMI_APP_ADAPTOR_VOICE_MSG_SEND_CNF;
    msg->result = result;

    Hal_Send_Message((TASK_ID)gui_task_id, (void *)msg);
#else /*for PC simulator*/
    app_adaptor_voice_msg_handle_send_cnf(result);
#endif
}

void voice_msg_update_contact_from_phonebook_ind(voice_msg_update_contact_t * update)
{
    if(NULL == update) return;

    printf("%s: update type %d, index %d, contact type %d\n",
           __FUNCTION__, update->type, update->index, update->contact_type);

    lv_obj_t * par = lv_scr_act();
    lv_obj_t * activity_obj;
    lv_voice_msg_chat_ext_t * chat_ext = NULL;
    lv_voice_msg_incoming_ext_t * incom_ext = NULL;
    lv_obj_t * voice_msg_obj = NULL;
    lv_voice_msg_ext_t * ext = NULL;
    voice_msg_contact_t ** cur_contact = NULL;
    _LV_LL_READ((par)->child_ll, activity_obj) {
        lv_watch_activity_ext_t * act_ext = lv_obj_get_ext_attr(activity_obj);
        if(NULL == act_ext) continue;

        if(ACT_ID_VOICE_MSG_CHAT == act_ext->actId) {
            lv_obj_t * obj = NULL;
            lv_watch_get_child_obj(activity_obj, lv_watch_obj_signal, &obj);
            chat_ext = lv_obj_get_ext_attr(obj);
            chat_ext->contact_list_update = true;
            cur_contact = &chat_ext->cur_contact;
            break;
        } else if(ACT_ID_VOICE_MSG == act_ext->actId) {
            lv_obj_t * obj = NULL;
            lv_watch_get_child_obj(activity_obj, lv_watch_obj_signal, &obj);
            voice_msg_obj = obj;
            ext = lv_obj_get_ext_attr(obj);
            break;
        } else if(ACT_ID_VOICE_MSG_INCOMING == act_ext->actId) {
            lv_obj_t * obj = NULL;
            lv_watch_get_child_obj(activity_obj, lv_watch_obj_signal, &obj);
            incom_ext = lv_obj_get_ext_attr(obj);
            cur_contact = &incom_ext->cur_contact;
            break;
        }
    }

    lv_ll_t * contact_list = NULL;
    if(chat_ext) {
        contact_list = chat_ext->contact_list;
    } else if(ext) {
        contact_list = ext->contact_list;
    } else if(incom_ext) {
        contact_list = incom_ext->contact_list;
    }

    bool result;
    if(contact_list) {
        result = voice_msg_update_contact_list_from_pb(update, contact_list, cur_contact);
    } else {
        result = voice_msg_update_nvm_from_pb(update);
    }
    if(false == result) {
        printf("update failure or not contact updated in %s\n", __FUNCTION__);
        if(update->old_name) lv_mem_free(update->old_name);
        if(update->new_name) lv_mem_free(update->new_name);
        if(update->number) lv_mem_free(update->number);
        lv_mem_free(update);
        return;
    }

    if(voice_msg_obj) {
        lv_obj_clean(voice_msg_obj);
        if((VOICE_MSG_UPDATE_TYPE_DELETE == update->type) && (0 == update->index)) {
            voice_msg_no_contacts_create(voice_msg_obj);
        } else {
            voice_msg_contacts_create(voice_msg_obj);
        }
    }

    if(update->old_name) lv_mem_free(update->old_name);
    if(update->new_name) lv_mem_free(update->new_name);
    if(update->number) lv_mem_free(update->number);
    lv_mem_free(update);
}

void voice_msg_send_emo_event_cb(lv_obj_t * img, lv_event_t e)
{
    if(LV_EVENT_CLICKED == e) {
        lv_voice_msg_chat_ext_t * ext = voice_msg_get_ext(ACT_ID_VOICE_MSG_CHAT);
        voice_msg_chat_stop_voice(ext);
        voice_msg_create_expression(NULL);
    }
}

void voice_msg_touch_spk_event_cb(lv_obj_t * img, lv_event_t e)
{
    if(LV_EVENT_LONG_PRESSED == e) {
        printf("%s\n", __FUNCTION__);
        lv_voice_msg_chat_ext_t * ext = voice_msg_get_ext(ACT_ID_VOICE_MSG_CHAT);
        LV_ASSERT_MEM(ext);

        if(0 == ext->old_ext.element_click_en) {
            /*sliding to the right, not click button*/
            return;
        }

        voice_msg_set_slide_enable(&ext->old_ext, false); /*prevent to slide out while recording*/
        voice_msg_chat_stop_voice(ext);
        voice_msg_create_speak();
        ext->buffer = (uint8_t *)lv_mem_alloc(VOICE_MSG_MAX_RECORD_SIZE);
        Hal_Audio_Manage_Start_Req(AUDIO_CTRL_PRIORITY_5,voice_msg_audio_ctrl_callback);
#ifdef BUILD_IN_PC_SIMULATOR /*for pc test*/
        voice_msg_test_record_buffer_start_req(ext->buffer,
                                               VOICE_MSG_MAX_RECORD_SIZE,
                                               VOICE_MSG_MAX_RECORD_DURATION * 1000,
                                               voice_msg_chat_record_cb);
#else
        Hal_Record_Buffer_Start_Req(ext->buffer,
                                    VOICE_MSG_MAX_RECORD_SIZE,
                                    VOICE_MSG_MAX_RECORD_DURATION * 1000,
                                    voice_msg_chat_record_cb);
#endif
    } else if(LV_EVENT_CLICKED == e || LV_EVENT_PRESS_LOST == e) {
        if(lv_watch_get_activity_obj(ACT_ID_VOICE_MSG_SPEAK)) {
            lv_voice_msg_chat_ext_t * ext = voice_msg_get_ext(ACT_ID_VOICE_MSG_CHAT);
            LV_ASSERT_MEM(ext);

            voice_msg_set_slide_enable(&ext->old_ext, true); /*reset slide_enable at the end of recording*/
#if VOICE_MSG_SLIDE_UP_TO_CANCEL_RECORDING != 0
            lv_indev_t * indev = lv_indev_get_act();
            if(indev) {
                lv_coord_t point_y = indev->proc.types.pointer.last_point.y;
                if(point_y < img->coords.y1) {
                    printf("%s: img y %d, last point %d\n", __FUNCTION__, img->coords.y1, point_y);
                    /*last point is above img, consider as sliding up and cancel recording*/
                    if(ext->buffer) {
                        voice_msg_record_buffer_stop_req();
                        lv_mem_free(ext->buffer);
                        ext->buffer = NULL;
                        voice_msg_del_interface(ACT_ID_VOICE_MSG_SPEAK);
                        Hal_Audio_Manage_End_Ind(AUDIO_CTRL_PRIORITY_5);
                    }
                }
            }
#endif

            bool result = voice_msg_send_voice(ext);
            voice_msg_del_interface(ACT_ID_VOICE_MSG_SPEAK);
            if(result) voice_msg_create_send(NULL);
        }
    }
}

void voice_msg_mt_call_ind(void)
{
    voice_msg_del_interface(ACT_ID_VOICE_MSG_SPEAK);

    lv_voice_msg_chat_ext_t * chat_ext = voice_msg_get_ext(ACT_ID_VOICE_MSG_CHAT);
    if(chat_ext) {
        printf("%s: ACT_ID_VOICE_MSG_CHAT\n", __FUNCTION__);
        voice_msg_chat_stop_voice(chat_ext);

        /*send voice*/
        if(chat_ext->buffer) {
            voice_msg_set_slide_enable(&chat_ext->old_ext, true); /*reset slide_enable at the end of recording*/
#if 0
            uint32_t duration_ms;
            uint32_t size;
            Hal_Record_Buffer_Stop_Req(&size, &duration_ms);
            lv_mem_free(chat_ext->buffer);
            chat_ext->buffer = NULL;
#else
            voice_msg_send_voice(chat_ext);
#endif
        }
    }

#if VOICE_MSG_ENABLE_ONE_UNREAD_MSG != 0
    lv_voice_msg_incoming_ext_t * incoming_ext = voice_msg_get_ext(ACT_ID_VOICE_MSG_INCOMING);
    if(incoming_ext) {
        printf("%s: ACT_ID_VOICE_MSG_INCOMING\n", __FUNCTION__);
        if(true == incoming_ext->voice_playing) {
            if(0 == incoming_ext->voice_size) {
                Hal_File_Play_End();
            } else {
                Hal_Tone_Play_End();
            }
            lv_anim_del(incoming_ext->img_voice_anim, NULL);
            lv_img_set_src(incoming_ext->img_voice_anim, ICON_PLAYING3);
            lv_watch_set_free_num(incoming_ext->img_voice_anim, 0);
        }
    }
#endif
}

void voice_msg_audio_ctrl_callback(AUDIO_CTRL_PRIORITY priority)
{
    printf("%s,priority is %d\n", __FUNCTION__,priority);
    if(AUDIO_CTRL_PRIORITY_5 == priority)
    {
        if(false == watch_is_ready()) {
            printf("%s: not handle voice msg during power off\n", __FUNCTION__);
            return;
        }
        voice_msg_high_priority_task_end();
    }
    else if(AUDIO_CTRL_PRIORITY_5 > priority)
    {
        voice_msg_mt_call_ind();
        Hal_Audio_Manage_End_Ind(AUDIO_CTRL_PRIORITY_5);
    }
}

#if 0
void voice_msg_call_end(void)
{
    if(false == watch_is_ready()) {
        printf("%s: not handle voice msg during power off\n", __FUNCTION__);
        return;
    } else if(voice_msg_is_higher_prio_task_present(ACT_ID_PHONE)) {
        printf("%s: not handle voice msg during other higher priority task\n", __FUNCTION__);
        return;
    }

    voice_msg_high_priority_task_end();
}

void voice_msg_alarm_end(void)
{
    printf("%s\n", __FUNCTION__);

    if(false == watch_is_ready()) {
        printf("%s: not handle voice msg during power off\n", __FUNCTION__);
        return;
    } else if(voice_msg_is_higher_prio_task_present(ACT_ID_ALARM)) {
        printf("%s: not handle voice msg during other higher priority task\n", __FUNCTION__);
        return;
    }

    voice_msg_high_priority_task_end();
}

void voice_msg_speech_recog_end(void)
{
    printf("%s\n", __FUNCTION__);

    if(false == watch_is_ready()) {
        printf("%s: not handle voice msg during power off\n", __FUNCTION__);
        return;
    }

    voice_msg_high_priority_task_end();
}

void voice_msg_mt_call_ind(void)
{
    voice_msg_del_interface(ACT_ID_VOICE_MSG_SPEAK);

    lv_voice_msg_chat_ext_t * chat_ext = voice_msg_get_ext(ACT_ID_VOICE_MSG_CHAT);
    if(chat_ext) {
        printf("%s: ACT_ID_VOICE_MSG_CHAT\n", __FUNCTION__);
        voice_msg_chat_stop_voice(chat_ext);

        /*not send voice*/
        if(chat_ext->buffer) {
            voice_msg_set_slide_enable(&chat_ext->old_ext, true); /*reset slide_enable at the end of recording*/
#if 0
            uint32_t duration_ms;
            uint32_t size;
            Hal_Record_Buffer_Stop_Req(&size, &duration_ms);
            lv_mem_free(chat_ext->buffer);
            chat_ext->buffer = NULL;
#else
            voice_msg_send_voice(chat_ext);
#endif
        }
    }

#if VOICE_MSG_ENABLE_ONE_UNREAD_MSG != 0
    lv_voice_msg_incoming_ext_t * incoming_ext = voice_msg_get_ext(ACT_ID_VOICE_MSG_INCOMING);
    if(incoming_ext) {
        printf("%s: ACT_ID_VOICE_MSG_INCOMING\n", __FUNCTION__);
        if(true == incoming_ext->voice_playing) {
            voice_msg_play_end(incoming_ext->voice_size);
            lv_anim_del(incoming_ext->img_voice_anim, NULL);
            lv_img_set_src(incoming_ext->img_voice_anim, ICON_PLAYING3);
            voice_msg_set_user_num(incoming_ext->img_voice_anim, 0);
        }
    }
#endif
}

void voice_msg_alarm_ind(void)
{
    printf("%s\n", __FUNCTION__);
    voice_msg_mt_call_ind();
}
#endif

void voice_msg_shut_down(void)
{
    printf("%s\n", __FUNCTION__);

    lv_voice_msg_incoming_ext_t * incom_ext = voice_msg_get_ext(ACT_ID_VOICE_MSG_INCOMING);
    if(incom_ext) {
        voice_msg_write_nvm(incom_ext->contact_list);
        return;
    }
    lv_voice_msg_chat_ext_t * chat_ext = voice_msg_get_ext(ACT_ID_VOICE_MSG_CHAT);
    if(chat_ext) {
        voice_msg_write_nvm(chat_ext->contact_list);
    } else {
        lv_voice_msg_ext_t * ext = voice_msg_get_ext(ACT_ID_VOICE_MSG);
        if(ext) {
            voice_msg_write_nvm(ext->contact_list);
        }
    }
}

void * voice_msg_get_ext(lv_watch_Activity_Id_t actId)
{
    lv_obj_t * obj = voice_msg_get_obj(actId);
    if(NULL == obj) return NULL;

    if(obj) {
        void * ext = lv_obj_get_ext_attr(obj);
        return ext;
    }
    return NULL;
}

uint32_t voice_msg_read_file_size(char * file_name)
{
    printf("%s: read file %s\n", __FUNCTION__, file_name);

    uint32_t size = 0;
    lv_fs_file_t * pfile = lv_mem_alloc(sizeof(lv_fs_file_t));
    memset(pfile,0,sizeof(lv_fs_file_t));
    lv_fs_open(pfile, (const char *)file_name, LV_FS_MODE_RD);
    if(NULL == pfile->file_d) {
        printf("%s: please check file %s\n", __FUNCTION__, file_name);
    } else {
        if(LV_FS_RES_OK != lv_fs_size(pfile, &size)) {
            printf("%s: read file size %d failure\n", __FUNCTION__, size);
            size = 0;
        }
        lv_fs_close(pfile);
        lv_mem_free(pfile);
    }

    return size;
}

void * voice_msg_read_file_data(char * file_name, uint8_t msg_type, uint32_t * data_size)
{
    printf("%s: read file %s\n", __FUNCTION__, file_name);

    void * data = NULL;
    uint32_t size = 0;

    lv_fs_file_t * pfile = lv_mem_alloc(sizeof(lv_fs_file_t));
    memset(pfile,0,sizeof(lv_fs_file_t));
    lv_fs_open(pfile, (const char *)file_name, LV_FS_MODE_RD);
    if(NULL == pfile->file_d) {
        printf("%s: please check file\n", __FUNCTION__);
    } else {
        if(LV_FS_RES_OK == lv_fs_size(pfile, &size)) {
            if(VOICE_MSG_TYPE_TEXT == msg_type) {
                data = lv_mem_alloc(size + 1);
                memset(data, 0, (size + 1));
            } else {
                data = lv_mem_alloc(size);
            }
            lv_fs_seek(pfile, SEEK_SET);
            lv_fs_read(pfile, data, size, NULL);
        } else {
            printf("%s: read file size %d failure\n", __FUNCTION__, size);
            size = 0;
        }
        lv_fs_close(pfile);
        lv_mem_free(pfile);
    }

    if(data_size) {
        *data_size = size;
    }

    return data;
}

void voice_msg_fill_chat_id(app_adaptor_voice_msg_chat_id_t * id, voice_msg_contact_t * contact)
{
    id->name = NULL;
    id->number = NULL;
    if(VOICE_MSG_FAMILY_GROUP == contact->index) {
        id->chat_type = WATCH_VOICE_MSG_FAMILY_GROUP_CHAT;
        id->name = voice_msg_copy_text(WATCH_TEXT_ID_FAMILY_GROUP);
    } else if(VOICE_MSG_FRIENDS_GROUP == contact->index) {
        id->chat_type = WATCH_VOICE_MSG_FRIENDS_GROUP_CHAT;
        id->name = voice_msg_copy_text(WATCH_TEXT_ID_FRIENDS_GROUP);
    } else {
        id->chat_type = WATCH_VOICE_MSG_SINGLE_CHAT;
        uint8_t text_len = strlen(contact->name) + 1;
        id->name = (char *)lv_mem_alloc(text_len);
        memcpy(id->name, contact->name, text_len);
        text_len = strlen(contact->number) + 1;
        id->number = (char *)lv_mem_alloc(text_len);
        memcpy(id->number, contact->number, text_len);
    }
}

void voice_msg_send_photo(void * buf, uint32_t size, voice_msg_contact_t * contact)
{
    printf("%s: buf %p, size %d\n", __FUNCTION__, buf, size);

    if((NULL == buf) || (0 == size) || (NULL == contact)) {
        return;
    }

    printf("%s: name %s, number %s, mark %d\n",
           __FUNCTION__, contact->name, contact->number, contact->mark);

    voice_msg_create_send(NULL);

    app_adaptor_voice_msg_t * photo_msg = (app_adaptor_voice_msg_t *)lv_mem_alloc(sizeof(app_adaptor_voice_msg_t));
    memset(photo_msg, 0, sizeof(app_adaptor_voice_msg_t));
    app_adaptor_voice_msg_chat_id_t id;
    voice_msg_fill_chat_id(&id, contact);
    photo_msg->chat_type = id.chat_type;
    photo_msg->name = id.name;
    photo_msg->number = id.number;
    photo_msg->count = 1;
    photo_msg->msg = (app_adaptor_voice_msg_info_t *)lv_mem_alloc(sizeof(app_adaptor_voice_msg_info_t));
    memset(photo_msg->msg, 0, sizeof(app_adaptor_voice_msg_info_t));
    photo_msg->msg->direction = WATCH_VOICE_MSG_FROM_UI;
    hal_rtc_t time;
    Hal_Rtc_Gettime(&time);
    photo_msg->msg->time.year = time.tm_year;
    photo_msg->msg->time.month = time.tm_mon;
    photo_msg->msg->time.day = time.tm_mday;
    photo_msg->msg->time.hour = time.tm_hour;
    photo_msg->msg->time.min = time.tm_min;
    photo_msg->msg->type = WATCH_VOICE_MSG_TYPE_PHOTO;
    photo_msg->msg->data_type = WATCH_VOICE_DATA_TYPE_BUFFER;
    photo_msg->msg->content.img.data_size = size;
    photo_msg->msg->content.img.data = (uint8_t *)lv_mem_alloc(size);
    memcpy(photo_msg->msg->content.img.data, buf, size);
    app_adaptor_voice_msg_send_req(photo_msg);

    /*delete voice msg*/
    voice_msg_del_interface(ACT_ID_VOICE_MSG);
}

void voice_msg_enlarge_photo(void * small_photo)
{
    voice_msg_create_enlarged_photo(small_photo);
}

void voice_msg_set_user_num(lv_obj_t * obj, uint32_t user_num)
{
    lv_obj_user_data_t * user_data = lv_obj_get_user_data_ptr(obj);
    user_data->user_num = user_num;
}

void voice_msg_set_user_ptr(lv_obj_t * obj, void * user_ptr)
{
    lv_obj_user_data_t * user_data = lv_obj_get_user_data_ptr(obj);
    user_data->user_data = user_ptr;
}

uint32_t voice_msg_get_user_num(lv_obj_t * obj)
{
    lv_obj_user_data_t * user_data = lv_obj_get_user_data_ptr(obj);
    return user_data->user_num;
}

void * voice_msg_get_user_ptr(lv_obj_t * obj)
{
    lv_obj_user_data_t * user_data = lv_obj_get_user_data_ptr(obj);
    return user_data->user_data;
}

void voice_msg_play_end(uint32_t len)
{
#ifdef BUILD_IN_PC_SIMULATOR /*for pc test*/
    if(0 == len) {
        // file
        voice_msg_test_file_play_end();
    } else {
        // buffer
        voice_msg_test_tone_play_end();
    }
#else
    if(0 == len) {
        // file
        Hal_NFFS_File_Play_End();
    } else {
        // buffer
        Hal_Tone_Play_End();
    }
#endif
}

void Voice_Msg_Play_Onetime(void * buffer, uint32_t len, HAL_SPEAKER_GAIN volume, CB_FUNC func,
                            void * para)
{
    printf("%s: len %d\n", __FUNCTION__, len);
    if(!func) func = voice_msg_msg_incoming_over_cb;
#ifdef BUILD_IN_PC_SIMULATOR /*for pc test*/
    if(0 == len) {
        // file
        voice_msg_test_file_play_onetime(buffer, volume, func, para);
    } else {
        // buffer
        voice_msg_test_tone_play_onetime(buffer, len, volume, func, para);
    }
#else
    if(0 == len) {
        // file
        Hal_NFFS_File_Play_Onetime(buffer, volume, func, para);
    } else {
        // buffer
        Hal_Media_Tone_Play_Onetime(buffer, len, volume, func, para);
    }
#endif
}

/**********************
 *   STATIC FUNCTIONS
 **********************/
static uint32_t voice_msg_record_buffer_stop_req(void)
{
    uint32_t duration_ms;
    uint32_t size;
#ifdef BUILD_IN_PC_SIMULATOR /*for pc test*/
    voice_msg_test_record_buffer_stop_req(&size, &duration_ms);
#else
    Hal_Record_Buffer_Stop_Req(&size, &duration_ms);
#endif
    return size;
}

static void voice_msg_set_slide_enable(lv_watch_obj_ext_t * ext, bool enable)
{
    printf("%s: enable %d\n", __FUNCTION__, enable);
    if(enable) {
        ext->slide_enable = 1; /*enable to slide to the right*/
    } else {
        ext->slide_enable = 0; /*disable to slide to the right*/
    }
}

static void voice_msg_lcd_wakeup(void)
{
    printf("%s\n", __FUNCTION__);

    if(0 == Hal_Pm_Get_State()) {
        Hal_Pm_WakeUp();
        watch_set_lcd_status(true);
    } else {
        watch_set_lcd_status(true);
        watch_wakeup_time_reset();
    }
}

static bool voice_msg_is_higher_prio_task_present(lv_watch_Activity_Id_t exception)
{
    if((ACT_ID_PHONE != exception)
            && (phone_is_monitor_on() || lv_watch_get_activity_obj(ACT_ID_PHONE))) {
        return true;
    } else if((ACT_ID_ALARM != exception) && lv_watch_get_activity_obj(ACT_ID_ALARM)) {
        return true;
    } else if((ACT_ID_SPEECH_RECOG != exception) && lv_watch_get_activity_obj(ACT_ID_SPEECH_RECOG)) {
        return true;
    } else {
        return false;
    }
}

static bool voice_msg_compare_contacts(app_adaptor_voice_msg_t * msg, voice_msg_contact_t * contact)
{
    if((WATCH_VOICE_MSG_SINGLE_CHAT == msg->chat_type) && (NULL != msg->number)) {
        if(0 == strcmp(msg->number, contact->number)) {
            return true;
        }
    } else {
        if(0 == strcmp(msg->name, contact->name)) {
            return true;
        }
    }
    return false;
}

static char * voice_msg_copy_text(uint16_t text_id)
{
    const char * text = lv_lang_get_text(text_id);
    uint8_t len = strlen(text) + 1;
    char * str = (char *)lv_mem_alloc(len);
    memcpy(str, text, len);

    return str;
}

static nv_watch_voice_msg_t * voice_msg_read_index_nvm(void)
{
    uint32_t length = sizeof(nv_watch_voice_msg_t);
    nv_watch_voice_msg_t * nvm = (nv_watch_voice_msg_t *)lv_mem_alloc(length);
    if(length != UI_NV_Read_Req(NV_SECTION_UI_VOICE_MSG, 0, length, (uint8_t *)nvm)) {
        printf("read pb index from nvm error in voice_msg_read_index_nvm\n");
        lv_mem_free(nvm);
        return NULL;
    }

    return nvm;
}

static nv_watch_phonebook_t * voice_msg_read_pb_nvm(void)
{
    uint32_t length = sizeof(nv_watch_phonebook_t);
    nv_watch_phonebook_t * nvm_pb = (nv_watch_phonebook_t *)lv_mem_alloc(length);
    if(length != UI_NV_Read_Req(NV_SECTION_UI_PHONEBOOK, 0, length, (uint8_t *)nvm_pb)) {
        printf("read pb from nvm error in voice_msg_read_pb_nvm\n");
        lv_mem_free(nvm_pb);
        return NULL;
    }

    return nvm_pb;
}

static lv_ll_t * voice_msg_read_nvm(void)
{
    lv_ll_t * contact_list = (lv_ll_t *)lv_mem_alloc(sizeof(lv_ll_t));
    _lv_ll_init(contact_list, sizeof(voice_msg_contact_t));

    nv_watch_voice_msg_t * nvm = voice_msg_read_index_nvm();

    if(0xFF == nvm->index[0]) {
        lv_mem_free(nvm);
        return contact_list;
    }

    nv_watch_phonebook_t * nvm_pb = voice_msg_read_pb_nvm();

    voice_msg_contact_t * contact;
    for(uint8_t i = 0; i <= NV_WATCH_MAX_VOICE_MSG_CHAT_NUM; i++) {
        if(0xFF == nvm->index[i]) break;

        contact = _lv_ll_ins_tail(contact_list);
        contact->index = nvm->index[i];
        if(VOICE_MSG_FAMILY_GROUP == contact->index) {
            const char * text = lv_lang_get_text(WATCH_TEXT_ID_FAMILY_GROUP);
            strncpy(contact->name, text, NV_CONTACTS_MAX_NAME_LEN);
            memset(contact->number, 0, NV_CONTACTS_MAX_NUMBER_LEN);
            contact->portrait_id = VOICE_MSG_PORTRAIT_ID_FAMILY_GROUP;
        } else if(VOICE_MSG_FRIENDS_GROUP == contact->index) {
            const char * text = lv_lang_get_text(WATCH_TEXT_ID_FRIENDS_GROUP);
            strncpy(contact->name, text, NV_CONTACTS_MAX_NAME_LEN);
            memset(contact->number, 0, NV_CONTACTS_MAX_NUMBER_LEN);
            contact->portrait_id = VOICE_MSG_PORTRAIT_ID_FRIENDS_GROUP;
        } else {
            memcpy(contact->name, nvm_pb->info[contact->index].name, NV_CONTACTS_MAX_NAME_LEN);
            memcpy(contact->number, nvm_pb->info[contact->index].number, NV_CONTACTS_MAX_NUMBER_LEN);
            contact->portrait_id = nvm_pb->info[contact->index].portrait_id;
        }
        contact->mark = nvm->mark[i];
    }
    lv_mem_free(nvm);
    lv_mem_free(nvm_pb);

    return contact_list;
}

static void voice_msg_write_nvm(lv_ll_t * contact_list)
{
    uint32_t length = sizeof(nv_watch_voice_msg_t);
    nv_watch_voice_msg_t * nvm = (nv_watch_voice_msg_t *)lv_mem_alloc(length);
    memset(nvm, 0xFF, length);
    voice_msg_contact_t * contact = _lv_ll_get_head(contact_list);
    uint8_t index = 0;
    while(contact) {
        nvm->index[index] = contact->index;
        nvm->mark[index] = contact->mark;
        index++;
        contact = _lv_ll_get_next(contact_list, contact);
    }
    if(length != UI_NV_Write_Req(NV_SECTION_UI_VOICE_MSG, 0, length, (uint8_t *)nvm)) {
        printf("write pb index to nvm error in voice_msg_write_nvm\n");
    }
    lv_mem_free(nvm);
}

static bool voice_msg_update_nvm(app_adaptor_voice_msg_t * msg)
{
    nv_watch_voice_msg_t * nvm = voice_msg_read_index_nvm();
    nv_watch_phonebook_t * nvm_pb = voice_msg_read_pb_nvm();

    const char * family_group = lv_lang_get_text(WATCH_TEXT_ID_FAMILY_GROUP);
    const char * friends_group = lv_lang_get_text(WATCH_TEXT_ID_FRIENDS_GROUP);
    bool found_flag = false;
    for(uint8_t i = 0; i < NV_WATCH_MAX_VOICE_MSG_CHAT_NUM; i++) {
        if(0xFF == nvm->index[i]) {
            break;
        } else if(((VOICE_MSG_FAMILY_GROUP == nvm->index[i]) && (0 == strcmp(msg->name, family_group)))
                  || ((VOICE_MSG_FRIENDS_GROUP == nvm->index[i]) && (0 == strcmp(msg->name, friends_group)))
                  || ((VOICE_MSG_FAMILY_GROUP > nvm->index[i]) && (0 == strcmp(msg->name, nvm_pb->info[nvm->index[i]].name)))) {
            found_flag = true;
            if(0xFF == nvm->mark[i]) nvm->mark[i] = 0;
            nvm->mark[i]++;
            if(0 == i) {
                break;
            }
#if VOICE_MSG_SORT_CONTACTS_BY_MSG_TIME != 0
            /*move the contact index to the top*/
            uint8_t temp_mark = nvm->mark[i];
            uint8_t temp_index = nvm->index[i];
            memmove(&nvm->index[1], &nvm->index[0], i);
            nvm->index[0] = temp_index;
            memmove(&nvm->mark[1], &nvm->mark[0], i);
            nvm->mark[0] = temp_mark;
#endif
            break;
        }
    }
    if(true == found_flag) {
        uint32_t length = sizeof(nv_watch_voice_msg_t);
        if(length != UI_NV_Write_Req(NV_SECTION_UI_VOICE_MSG, 0, length, (uint8_t *)nvm)) {
            printf("write contact index to nvm error in voice_msg_update_nvm\n");
            return false;
        }
    }

    lv_mem_free(nvm);
    lv_mem_free(nvm_pb);

    return found_flag;
}

static void voice_msg_recover_buffed_msg(app_adaptor_voice_msg_t * msg)
{
    if(NULL == msg) return;
    if(NULL == msg->msg) return;
    if(NULL == msg->name) {
        if(WATCH_VOICE_MSG_FAMILY_GROUP_CHAT == msg->chat_type) {
            msg->name = voice_msg_copy_text(WATCH_TEXT_ID_FAMILY_GROUP);
        } else if(WATCH_VOICE_MSG_FRIENDS_GROUP_CHAT == msg->chat_type) {
            msg->name = voice_msg_copy_text(WATCH_TEXT_ID_FRIENDS_GROUP);
        }
    }

    printf("%s: chat type %d, rcv msg from %s\n", __FUNCTION__, msg->chat_type, msg->name);

    /*note: nvm has been updated in app_adaptor_voice_msg_rcv_ind*/

    lv_ll_t * contact_list = voice_msg_read_nvm();
    voice_msg_contact_t * node = _lv_ll_get_head(contact_list);
    voice_msg_contact_t * contact = NULL;
    uint16_t num_unread_msg = 0;
    while(node) {
        if(true == voice_msg_compare_contacts(msg, node)) {
            contact = node;
        }
        if(0xFF != node->mark) {
            num_unread_msg += node->mark;
        }
        node = _lv_ll_get_next(contact_list, node);
    }
    if(NULL == contact) {
        printf("%s: warning, contact not found\n", __FUNCTION__);
        voice_msg_free_msgs(msg);
        return;
    }

#if VOICE_MSG_ENABLE_ONE_UNREAD_MSG != 0
    if(1 == num_unread_msg) {
        voice_msg_create_one_unread_msg(msg, contact_list, contact);
    } else {
        voice_msg_create_multi_unread_msgs(NULL, num_unread_msg, contact_list, contact);
    }
#else
    voice_msg_create_multi_unread_msgs(NULL, num_unread_msg, contact_list, contact);
#endif

    voice_msg_free_msgs(msg);
}

static void voice_msg_high_priority_task_end(void)
{
    lv_voice_msg_incoming_ext_t * ext = voice_msg_get_ext(ACT_ID_VOICE_MSG_INCOMING);
    bool wakeup = false;
    printf("%s: msg_buf %p, ext %p\n", __FUNCTION__, msg_buf, ext);
    if(ext) {
        if(ext->update) {
            wakeup = true;
            ext->update = false;
        }
    } else if(msg_buf) {
        voice_msg_recover_buffed_msg(msg_buf);
        msg_buf = NULL;
        wakeup = true;
    } else {
        return;
    }

    if(wakeup) {
        voice_msg_lcd_wakeup();
    }
}

static void voice_msg_update_contact_list(lv_voice_msg_ext_t * ext)
{
    if(NULL == ext) return;

    lv_obj_t * page_scrl = lv_page_get_scrl(ext->page);
    lv_obj_clean(page_scrl);
    voice_msg_contact_list_create(ext, lv_obj_get_y(page_scrl));
#if VOICE_MSG_SORT_CONTACTS_BY_MSG_TIME != 0
    lv_obj_t * btn = lv_obj_get_child_back(page_scrl, NULL);
    if(btn) {
        lv_page_focus(ext->page, btn, 0);
    }
#endif
}

static void voice_msg_chat_stop_voice(lv_voice_msg_chat_ext_t * ext)
{
    if(NULL == ext) return;
    if(NULL == ext->list) return;
    lv_voice_msg_list_ext_t * list_ext = lv_obj_get_ext_attr(ext->list);
    if(NULL == list_ext) return;
    if(list_ext->voice_playing_anim.var) {
        lv_obj_t * bubble_img = lv_obj_get_parent(list_ext->voice_playing_anim.var);
        lv_obj_t * len_label = lv_obj_get_child_back(bubble_img, list_ext->voice_playing_anim.var);
        uint32_t voice_size = voice_msg_get_user_num(len_label);
        printf("%s: voice_size %d\n", __FUNCTION__, voice_size);
        voice_msg_play_end(voice_size);
        Hal_Audio_Manage_End_Ind(AUDIO_CTRL_PRIORITY_5);
        lv_anim_del(list_ext->voice_playing_anim.var, NULL);
        if(VOICE_MSG_IS_MSG_FROM_SERVICE(voice_msg_get_user_num(bubble_img))) {
            lv_img_set_src(list_ext->voice_playing_anim.var, ICON_PLAYING3);
        } else {
            lv_img_set_src(list_ext->voice_playing_anim.var, ICON_MY_PLAYING3);
        }
        list_ext->voice_playing_anim.var = NULL;
    }
}

static void voice_msg_common_prepare_destory(lv_obj_t * activity_obj)
{
    printf("%s\n", __FUNCTION__);
    lv_watch_png_cache_all_free();
}

static void voice_msg_prepare_destory(lv_obj_t * activity_obj)
{
    printf("%s\n", __FUNCTION__);

    if(lv_watch_get_activity_obj(ACT_ID_VOICE_MSG_CHAT)) {
        printf("%s: error, delete ACT_ID_VOICE_MSG before ACT_ID_VOICE_MSG_CHAT\n", __FUNCTION__);
        return;
    }

    lv_voice_msg_ext_t * ext = voice_msg_get_ext(ACT_ID_VOICE_MSG);
    if(NULL == ext) return;
    if(VOICE_MSG_FROM_ALBUM == ext->other_app) {
        album_photo_share_flag_reset();
    } else if (VOICE_MSG_FROM_IMGVIEWER == ext->other_app) {
#if USE_LV_WATCH_IMGVIEWER != 0
        imgviewer_photo_share_flag_reset();
#endif
    }
    if(ext->contact_list) {
#if VOICE_MSG_TEST == 0
        voice_msg_write_nvm(ext->contact_list);
#elif VOICE_MSG_APP_SPECIFIC_TEST != 0
        voice_msg_write_nvm(ext->contact_list);
#endif
        _lv_ll_clear(ext->contact_list);
        lv_mem_free(ext->contact_list);
    }
    lv_style_reset(&(ext->style_cont_mark));
    lv_style_reset(&(ext->style_label_mark));
    lv_watch_png_cache_all_free();

#if VOICE_MSG_TEST != 0
#if VOICE_MSG_APP_SPECIFIC_TEST == 0
    voice_msg_test_clear_nvm();
    voice_msg_test_clear();
#endif
#endif
}

static lv_obj_t * voice_msg_get_obj(lv_watch_Activity_Id_t actId)
{
    lv_obj_t * activity_obj = lv_watch_get_activity_obj(actId);
    if(NULL == activity_obj) return NULL;

    lv_obj_t * obj = NULL;
    lv_watch_get_child_obj(activity_obj, lv_watch_obj_signal, &obj);
    return obj;
}

static void * voice_msg_get_img_src(uint8_t portrait_id)
{
    char * img_src = NULL;

    switch(portrait_id) {
        case WATCH_PORTRAIT_ID_FATHER:
            img_src = ICON_FATHER;
            break;
        case WATCH_PORTRAIT_ID_MOTHER:
            img_src = ICON_MOTHER;
            break;
        case WATCH_PORTRAIT_ID_PATERNAL_GRADFATHER:
            img_src = ICON_PATERNAL_GRADFATHER;
            break;
        case WATCH_PORTRAIT_ID_PATERNAL_GRADMOTHER:
            img_src = ICON_PATERNAL_GRADMOTHER;
            break;
        case WATCH_PORTRAIT_ID_MATERNAL_GRADFATHER:
            img_src = ICON_MATERNAL_GRADFATHER;
            break;
        case WATCH_PORTRAIT_ID_MATERNAL_GRADMOTHER:
            img_src = ICON_MATERNAL_GRADMOTHER;
            break;
        case WATCH_PORTRAIT_ID_UNCLE:
            img_src = ICON_UNCLE;
            break;
        case WATCH_PORTRAIT_ID_AUNT:
            img_src = ICON_AUNT;
            break;
        case WATCH_PORTRAIT_ID_BROTHER:
        case WATCH_PORTRAIT_ID_BOY:
            img_src = ICON_BOY;
            break;
        case WATCH_PORTRAIT_ID_SISTER:
        case WATCH_PORTRAIT_ID_GIRL:
            img_src = ICON_GIRL;
            break;
        case WATCH_PORTRAIT_ID_FAMILY:
        case WATCH_PORTRAIT_ID_OTHERS:
            img_src = ICON_FAMILY;
            break;
        case VOICE_MSG_PORTRAIT_ID_FAMILY_GROUP:
            img_src = ICON_RELATIVE;
            break;
        case VOICE_MSG_PORTRAIT_ID_FRIENDS_GROUP:
            img_src = ICON_FRIENDS;
            break;
        default:
            printf("warning! no icon corresponding to portrait_id %d", portrait_id);
            break;
    }

    return img_src;
}

static lv_res_t voice_msg_del_top_interface(void)
{
    lv_obj_t * activity_obj = lv_watch_get_top_activity_obj();
    if(NULL == activity_obj) return LV_RES_INV;

    lv_watch_activity_ext_t * ext = lv_obj_get_ext_attr(activity_obj);
    if(NULL == ext) return LV_RES_INV;
    if(ext->prepare_destory) {
        ext->prepare_destory(activity_obj);
    }
    lv_obj_del(activity_obj);

    return LV_RES_OK;
}

static void voice_msg_del_interface(lv_watch_Activity_Id_t act_id)
{
    lv_obj_t * activity_obj = lv_watch_get_activity_obj(act_id);
    if(NULL == activity_obj) return;

    lv_watch_activity_ext_t * ext = lv_obj_get_ext_attr(activity_obj);
    LV_ASSERT_MEM(ext);
    if(ext->prepare_destory) {
        ext->prepare_destory(activity_obj);
    }
    lv_obj_del(activity_obj);

    return;
}

static void voice_msg_add_group(uint8_t group_index, lv_ll_t * contact_list)
{
    printf("%s: add group for contact_index %d\n", __FUNCTION__, group_index);

    if(NULL == contact_list) {
        printf("%s: contact_list is NULL\n", __FUNCTION__);
        return;
    }

    if((VOICE_MSG_FAMILY_GROUP != group_index) && (VOICE_MSG_FRIENDS_GROUP != group_index)) {
        printf("%s: invalid group index %d\n", __FUNCTION__, group_index);
        return;
    }

    voice_msg_contact_t * contact = _lv_ll_ins_tail(contact_list);
    if(VOICE_MSG_FAMILY_GROUP == group_index) {
        const char * text = lv_lang_get_text(WATCH_TEXT_ID_FAMILY_GROUP);
        strncpy(contact->name, text, NV_CONTACTS_MAX_NAME_LEN);
        contact->portrait_id = VOICE_MSG_PORTRAIT_ID_FAMILY_GROUP;
    } else if(VOICE_MSG_FRIENDS_GROUP == group_index) {
        const char * text = lv_lang_get_text(WATCH_TEXT_ID_FRIENDS_GROUP);
        strncpy(contact->name, text, NV_CONTACTS_MAX_NAME_LEN);
        contact->portrait_id = VOICE_MSG_PORTRAIT_ID_FRIENDS_GROUP;
    }
    contact->index = group_index;
    memset(contact->number, 0, NV_CONTACTS_MAX_NUMBER_LEN);
    contact->mark = 0xFF;
}

static bool voice_msg_update_contact_list_from_pb(voice_msg_update_contact_t * update,
        lv_ll_t * contact_list,
        voice_msg_contact_t ** cur_contact)
{
    if(NULL == update) {
        /*update failure*/
        return false;
    }
    if(NULL == contact_list) {
        /*update failure*/
        return false;
    }

    if(VOICE_MSG_UPDATE_TYPE_ADD == update->type) {
        if(0 == update->index) {
            if(contact_list->head) {
                printf("%s: add admin failure because contacts exist!", __FUNCTION__);
                return false;
            }

#if VOICE_MSG_FAMILY_GROUP_CHAT_WITH_ADMIN_ONLY != 0
            /*add family group*/
            voice_msg_add_group(VOICE_MSG_FAMILY_GROUP, contact_list);
#endif

            /*add administrator*/
            voice_msg_contact_t * contact = _lv_ll_ins_tail(contact_list);
            strncpy(contact->name, update->new_name, NV_CONTACTS_MAX_NAME_LEN);
            strncpy(contact->number, update->number, NV_CONTACTS_MAX_NUMBER_LEN);
            contact->index = 0;
            contact->portrait_id = update->portrait_id;
            contact->mark = 0xFF;
        } else {
            voice_msg_contact_t * contact = _lv_ll_get_head(contact_list);
            while(contact) {
                if((VOICE_MSG_FAMILY_GROUP > contact->index)
                        && (update->index <= contact->index)) {
                    contact->index++;
                }
                contact = _lv_ll_get_next(contact_list, contact);
            }
            contact = _lv_ll_ins_tail(contact_list);
            strncpy(contact->name, update->new_name, NV_CONTACTS_MAX_NAME_LEN);
            strncpy(contact->number, update->number, NV_CONTACTS_MAX_NUMBER_LEN);
            contact->index = update->index;
            contact->portrait_id = update->portrait_id;
            contact->mark = 0xFF;

#if VOICE_MSG_FAMILY_GROUP_CHAT_WITH_ADMIN_ONLY != 1
            if((1 == update->standby_admin_count) && (1 == update->contact_type)) {
                /*add family group*/
                voice_msg_add_group(VOICE_MSG_FAMILY_GROUP, contact_list);
            } else if((2 == update->others_count) && (2 == update->contact_type)) {
                /*add friends group*/
                voice_msg_add_group(VOICE_MSG_FRIENDS_GROUP, contact_list);
            }
#endif
        }
    } else if(VOICE_MSG_UPDATE_TYPE_UPDATE == update->type) {
        voice_msg_contact_t * contact = _lv_ll_get_head(contact_list);
        while(contact) {
            if(update->index == contact->index) {
                if(update->new_name) {
                    /*name changed*/
                    strncpy(contact->name, update->new_name, NV_CONTACTS_MAX_NAME_LEN);
                    contact->portrait_id = update->portrait_id;
                }
                if(update->number) {
                    /*clear mark for unread message*/
                    contact->mark = 0xFF;
                    strncpy(contact->number, update->number, NV_CONTACTS_MAX_NUMBER_LEN);
                }
                break;
            }
            contact = _lv_ll_get_next(contact_list, contact);
        }
        if(NULL == contact) {
            /*not contact updated*/
            return false;
        }
    } else if(VOICE_MSG_UPDATE_TYPE_DELETE == update->type) {
        if(0 == update->index) {
            /*delete administrator, clear all of contacts*/
            _lv_ll_clear(contact_list);
            if(cur_contact) {
                *cur_contact = NULL;
            }
        } else {
            voice_msg_contact_t * contact = _lv_ll_get_head(contact_list);
            while(contact) {
                if(update->index == contact->index) {
                    if(cur_contact) {
                        if(*cur_contact == contact) {
                            *cur_contact = NULL;
                        }
                    }
                    _lv_ll_remove(contact_list, contact);
                    lv_mem_free(contact);
                    break;
                }
                contact = _lv_ll_get_next(contact_list, contact);
            }
            if(NULL == contact) {
                /*not contact updated*/
                return false;
            }

#if VOICE_MSG_FAMILY_GROUP_CHAT_WITH_ADMIN_ONLY != 1
            uint8_t group_index = 0xFF;
            if((0 == update->standby_admin_count) && (1 == update->contact_type)) {
                /*delete family group*/
                group_index = VOICE_MSG_FAMILY_GROUP;
            } else if((1 == update->others_count) && (2 == update->contact_type)) {
                /*delete friends group*/
                group_index = VOICE_MSG_FRIENDS_GROUP;
            }
            if(0xFF != group_index) {
                while(contact) {
                    if(group_index == contact->index) {
                        _lv_ll_remove(contact_list, contact);
                        lv_mem_free(contact);
                        break;
                    }
                    contact = _lv_ll_get_next(contact_list, contact);
                }
            }
#endif
        }
    }

    /*update success*/
    return true;
}

static bool voice_msg_update_nvm_from_pb(voice_msg_update_contact_t * update)
{
    if(NULL == update) {
        /*update failure*/
        return false;
    }

    nv_watch_voice_msg_t * nvm = voice_msg_read_index_nvm();
    if(NULL == nvm) {
        /*update failure*/
        return false;
    }

    if(VOICE_MSG_UPDATE_TYPE_ADD == update->type) {
        if(0 == update->index) {
            /*add administrator and family group*/
            memset(nvm, 0xFF, sizeof(nv_watch_voice_msg_t));
            uint8_t index = 0;
#if VOICE_MSG_FAMILY_GROUP_CHAT_WITH_ADMIN_ONLY != 0
            nvm->index[index++] = VOICE_MSG_FAMILY_GROUP;
#endif
            nvm->index[index] = 0; /*add administrator*/
        } else {
            uint8_t i;
            for(i = 0; i < NV_WATCH_MAX_VOICE_MSG_CHAT_NUM; i++) {
                if(0xFF == nvm->index[i]) {
                    nvm->index[i] = update->index;
                    break;
                }
                if((VOICE_MSG_FAMILY_GROUP > nvm->index[i]) && (update->index <= nvm->index[i])) {
                    nvm->index[i]++;
                }
            }
#if VOICE_MSG_FAMILY_GROUP_CHAT_WITH_ADMIN_ONLY != 1
            if((1 == update->standby_admin_count) && (1 == update->contact_type)) {
                /*add family group*/
                nvm->index[++i] = VOICE_MSG_FAMILY_GROUP;
            } else if((2 == update->others_count) && (2 == update->contact_type)) {
                /*add friends group*/
                nvm->index[++i] = VOICE_MSG_FRIENDS_GROUP;
            }
#endif
        }
    } else if(VOICE_MSG_UPDATE_TYPE_UPDATE == update->type) {
        for(uint8_t i = 0; i < NV_WATCH_MAX_VOICE_MSG_CHAT_NUM; i++) {
            if(0xFF == nvm->index[i]) {
                /*not contact updated*/
                return false;
            }
            if(update->index == nvm->index[i]) {
                if(update->number) {
                    nvm->mark[i] = 0xFF;
                }
                break;
            }
        }
    } else if(VOICE_MSG_UPDATE_TYPE_DELETE == update->type) {
        if(0 == update->index) {
            /*delete administrator, clear all of contacts*/
            memset(nvm, 0xFF, sizeof(nv_watch_voice_msg_t));
        } else {
            uint8_t node = 0xFF;
            for(uint8_t i = 0; i < NV_WATCH_MAX_VOICE_MSG_CHAT_NUM; i++) {
                if(0xFF == nvm->index[i]) {
                    break;
                }
                if(VOICE_MSG_FAMILY_GROUP > nvm->index[i]) {
                    if(update->index == nvm->index[i]) {
                        nvm->index[i] = 0xFF;
                        nvm->mark[i] = 0xFF;
                        node = i;
                    } else if(update->index < nvm->index[i]) {
                        nvm->index[i] -= 1;
                    }
                }
                if(node < i) {
                    nvm->index[i - 1] = nvm->index[i];
                    nvm->index[i] = 0xFF;
                    nvm->mark[i - 1] = nvm->mark[i];
                    nvm->mark[i] = 0xFF;
                }
            }
            if(0xFF == node) {
                /*not contact updated*/
                lv_mem_free(nvm);
                return false;
            }

#if VOICE_MSG_FAMILY_GROUP_CHAT_WITH_ADMIN_ONLY != 1
            uint8_t group_index = 0xFF;
            if((0 == update->standby_admin_count) && (1 == update->contact_type)) {
                /*delete family group*/
                group_index = VOICE_MSG_FAMILY_GROUP;
            } else if((1 == update->others_count) && (2 == update->contact_type)) {
                /*delete friends group*/
                group_index = VOICE_MSG_FRIENDS_GROUP;
            }
            if(0xFF != group_index) {
                for(uint8_t i = 0; i < NV_WATCH_MAX_VOICE_MSG_CHAT_NUM; i++) {
                    if(group_index == nvm->index[i]) {
                        nvm->index[i] = 0xFF;
                        nvm->mark[i] = 0xFF;
                        for(uint8_t j = i + 1; j < NV_WATCH_MAX_VOICE_MSG_CHAT_NUM; j++) {
                            if(0xFF == nvm->index[j]) {
                                break;
                            }
                            nvm->index[j - 1] = nvm->index[j];
                            nvm->mark[j - 1] = nvm->mark[j];
                            nvm->index[j] = 0xFF;
                            nvm->mark[j] = 0xFF;
                        }

                        break;
                    }
                }
            }
#endif
        }
    }

    uint32_t length = sizeof(nv_watch_voice_msg_t);
    if(length != UI_NV_Write_Req(NV_SECTION_UI_VOICE_MSG, 0, length, (uint8_t *)nvm)) {
        printf("write nvm error in %s\n", __FUNCTION__);
        lv_mem_free(nvm);
        return false;
    }

    lv_mem_free(nvm);

    return true;
}

static void voice_msg_record_task_cb(lv_task_t * task)
{
    lv_voice_msg_chat_ext_t * ext = voice_msg_get_ext(ACT_ID_VOICE_MSG_CHAT);
    if(NULL == ext) return;

    printf("%s: event %d\n", __FUNCTION__, ext->event);

    bool result;
    switch(ext->event) {
        case MCI_EVENT_EOS:
            voice_msg_set_slide_enable(&ext->old_ext, true); /*reset slide_enable at the end of recording*/
            result = voice_msg_send_voice(ext);
            voice_msg_del_interface(ACT_ID_VOICE_MSG_SPEAK);
            if(result) voice_msg_create_send(NULL);
            break;
        case MCI_EVENT_ERROR:
            voice_msg_set_slide_enable(&ext->old_ext, true); /*reset slide_enable at the end of recording*/
            if(ext->buffer) {
                lv_mem_free(ext->buffer);
                ext->buffer = NULL;
            }
            voice_msg_del_interface(ACT_ID_VOICE_MSG_SPEAK);
            break;
        default:
            break;
    }
}

static void voice_msg_chat_record_cb(MCI_EVNET_T event, MCI_INFO_T info_type, int32_t value)
{
    //printf("%s: event %d, type %d, value %d\n", __FUNCTION__, event, info_type, value);

    if(MCI_EVENT_INFO == event) {
        return;
    }

    lv_voice_msg_chat_ext_t * ext = voice_msg_get_ext(ACT_ID_VOICE_MSG_CHAT);
    if(NULL == ext) return;
    ext->event = event;
    lv_task_t * record_task = lv_task_create(voice_msg_record_task_cb, 10, LV_TASK_PRIO_HIGH, NULL);
    lv_task_once(record_task);
}

static void voice_msg_speaking_anim(lv_anim_t * a)
{
    lv_obj_t * img = a->var;
    uint8_t step = voice_msg_get_user_num(img);
    uint8_t record = step % 5;
    switch(record) {
        case 0:
            lv_img_set_src(img, ICON_RECORD1);
            break;
        case 1:
            lv_img_set_src(img, ICON_RECORD2);
            break;
        case 2:
            lv_img_set_src(img, ICON_RECORD3);
            break;
        case 3:
            lv_img_set_src(img, ICON_RECORD4);
            break;
        case 4:
            lv_img_set_src(img, ICON_RECORD5);
            break;
        default:
            break;
    }
    voice_msg_set_user_num(img, step + 1);
    if(0 == (step % 4)) {
        lv_voice_msg_speak_ext_t * speak_ext = voice_msg_get_ext(ACT_ID_VOICE_MSG_SPEAK);
        if(NULL == speak_ext) return;
        int8_t count_down = VOICE_MSG_MAX_RECORD_DURATION - step / 4;
        if(0 > count_down) {
            printf("%s: warning, count down %d\n", __FUNCTION__, count_down);
            return;
        }
        const char * content = lv_lang_get_text(WATCH_TEXT_ID_RELEASE_SENDING);
        uint8_t len = strlen(content) + 4;
        char * text = (char *)lv_mem_alloc(len);
        snprintf(text, len, "%s %d", content, count_down);
        lv_label_set_text(speak_ext->label, text);
        lv_mem_free(text);
        lv_obj_align(speak_ext->label, img, LV_ALIGN_OUT_BOTTOM_MID, 0, 6);
    }
}

static void voice_msg_release_send_event_cb(lv_obj_t * cont, lv_event_t e)
{
    if(LV_EVENT_CLICKED == e) {
        printf("%s\n", __FUNCTION__);
        lv_voice_msg_chat_ext_t * ext = voice_msg_get_ext(ACT_ID_VOICE_MSG_CHAT);
        if(NULL == ext) return;

        bool result = voice_msg_send_voice(ext);
        voice_msg_del_interface(ACT_ID_VOICE_MSG_SPEAK);
        if(result) voice_msg_create_send(NULL);
    }
}

static void voice_msg_create_speak(void)
{
    printf("%s\n", __FUNCTION__);

    lv_watch_activity_ext_t activity_ext;
    memset(&activity_ext, 0, sizeof(lv_watch_activity_ext_t));
    activity_ext.actId = ACT_ID_VOICE_MSG_SPEAK;
    activity_ext.create = NULL;
    activity_ext.prepare_destory = voice_msg_common_prepare_destory;
    lv_obj_t * activity_obj = lv_watch_creat_activity_obj(&activity_ext);
    LV_ASSERT_MEM(activity_obj);

    lv_obj_t * obj = lv_watch_obj_create(activity_obj);
    LV_ASSERT_MEM(obj);
    lv_voice_msg_speak_ext_t * ext = lv_obj_allocate_ext_attr(obj, sizeof(lv_voice_msg_speak_ext_t));

    lv_obj_t * cont = lv_cont_create(obj, NULL);
    lv_obj_set_size(cont, lv_obj_get_width(obj), lv_obj_get_height(obj));
    lv_obj_align(cont, obj, LV_ALIGN_CENTER, 0, 0);
    lv_obj_set_style_local_bg_color(cont,
                                    LV_CONT_PART_MAIN,
                                    LV_STATE_DEFAULT,
                                    LV_COLOR_MAKE(194, 252, 252));
    lv_obj_set_click(cont, true);
    lv_obj_set_event_cb(cont, voice_msg_release_send_event_cb);
    lv_obj_add_protect(cont, LV_PROTECT_PRESS_LOST);

    lv_obj_t * img = lv_img_create(cont, NULL);
    lv_img_set_src(img, ICON_RECORD1);
    lv_obj_align(img, cont, LV_ALIGN_IN_TOP_MID, 0, 16);
    voice_msg_set_user_num(img, 0);

    ext->label = lv_label_create(cont, NULL);
    lv_obj_add_style(ext->label, LV_LABEL_PART_MAIN, &lv_watch_font20_black);
    const char * rel_snd = lv_lang_get_text(WATCH_TEXT_ID_RELEASE_SENDING);
    uint8_t len = strlen(rel_snd) + 4;
    char * text = (char *)lv_mem_alloc(len);
    snprintf(text, len, "%s %d", rel_snd, VOICE_MSG_MAX_RECORD_DURATION);
    lv_label_set_text(ext->label, text);
    lv_mem_free(text);
    lv_obj_align(ext->label, img, LV_ALIGN_OUT_BOTTOM_MID, 0, 6);

    lv_obj_t * img_send_emo = lv_img_create(cont, NULL);
    lv_img_set_src(img_send_emo, ICON_SEND_EMO);

    lv_obj_t * img_release_snd = lv_img_create(cont, NULL);
    lv_img_set_src(img_release_snd, ICON_RELEASE_SND);

    if(LV_BIDI_DIR_RTL == lv_obj_get_base_dir(cont)) {
        lv_obj_align(img_release_snd, cont, LV_ALIGN_IN_BOTTOM_LEFT, 14, -8);
        lv_obj_align(img_send_emo, cont, LV_ALIGN_IN_BOTTOM_RIGHT, -14, -8);
    } else {
        lv_obj_align(img_send_emo, cont, LV_ALIGN_IN_BOTTOM_LEFT, 14, -8);
        lv_obj_align(img_release_snd, cont, LV_ALIGN_IN_BOTTOM_RIGHT, -14, -8);
    }

    lv_anim_t a = {};
    lv_anim_init(&a);
    lv_anim_set_var(&a, img);
    lv_anim_set_start_cb(&a, (lv_anim_ready_cb_t)voice_msg_speaking_anim);
    lv_anim_set_repeat_count(&a, LV_ANIM_REPEAT_INFINITE);
    lv_anim_set_time(&a, 249);
    lv_anim_start(&a);

#if VOICE_MSG_SLIDE_UP_TO_CANCEL_RECORDING != 0
    lv_obj_t * label_warning = lv_label_create(cont, NULL);
    lv_obj_set_style_local_text_font(label_warning,
                                     LV_LABEL_PART_MAIN,
                                     LV_STATE_DEFAULT,
                                     LV_THEME_WATCH_NIGHT_FONT_SMALL);
    if(LANG_EN == lv_lang_act()) {
        lv_label_set_align(label_warning, LV_LABEL_ALIGN_CENTER);
        lv_label_set_long_mode(label_warning, LV_LABEL_LONG_BREAK);
        lv_obj_set_width(label_warning, lv_obj_get_width(obj) * 2 / 3);
    }
    lv_obj_set_style_local_text_color(label_warning,
                                      LV_LABEL_PART_MAIN,
                                      LV_STATE_DEFAULT,
                                      LV_COLOR_RED);
    lv_label_set_text_id(label_warning, WATCH_TEXT_ID_SLIDE_UP_WARNING);
    lv_obj_align(label_warning, cont, LV_ALIGN_IN_TOP_MID, 0, 8);
#endif

    return;
}

static void voice_msg_note_create(lv_obj_t * obj, uint16_t text_id)
{
    lv_obj_t * label = lv_label_create(obj, NULL);
    lv_obj_add_style(label, LV_OBJ_PART_MAIN, &lv_watch_font20);

    lv_label_set_align(label, LV_LABEL_ALIGN_CENTER);
    lv_obj_set_click(label, false);
    lv_label_set_long_mode(label, LV_LABEL_LONG_BREAK);
    lv_obj_set_width(label, lv_obj_get_width(obj) - 50);
    lv_label_set_text_id(label, text_id);
    lv_obj_align(label, obj, LV_ALIGN_CENTER, 0, 0);

    return;
}

static void voice_msg_no_network_or_sim_create(lv_obj_t * obj)
{
    lv_obj_t * img = lv_img_create(obj, NULL);
    lv_obj_set_size(img, lv_obj_get_width(obj), lv_obj_get_height(obj));
    lv_obj_set_click(img, false);
    lv_obj_align(img, obj, LV_ALIGN_CENTER, 0, 0);
    lv_img_set_src(img, ICON_NONET_DEBT);

    return;
}

static void voice_msg_no_contacts_create(lv_obj_t * obj)
{
    lv_obj_t * label = lv_label_create(obj, NULL);
    lv_obj_add_style(label, LV_LABEL_PART_MAIN, &lv_watch_font20);

    lv_label_set_align(label, LV_LABEL_ALIGN_CENTER);
    lv_obj_set_click(label, false);
    lv_label_set_text_id(label, WATCH_TEXT_ID_NO_CONTACTS);
    lv_obj_align(label, obj, LV_ALIGN_CENTER, 0, 0);

    return;
}

static void voice_msg_title_img_back_event_cb(lv_obj_t * img, lv_event_t e)
{
    if(LV_EVENT_CLICKED == e) {
        voice_msg_del_top_interface();
    }
}

static lv_obj_t * voice_msg_create_title(lv_obj_t * cont, char * text)
{
    if(NULL == cont) return NULL;
    if(NULL == text) return NULL;

    lv_coord_t width_obj = lv_obj_get_width(cont);
    lv_obj_t * cont_title = lv_cont_create(cont, NULL);
    lv_coord_t height_label_cont = lv_obj_get_height(cont) / 6;
    lv_obj_set_size(cont_title, width_obj, height_label_cont);
    lv_obj_add_style(cont_title, LV_CONT_PART_MAIN, &lv_style_transp_tight);
    lv_cont_set_layout(cont_title, LV_LAYOUT_OFF);
    lv_obj_set_click(cont_title, true);
    lv_watch_obj_add_element(cont_title);
    lv_obj_clear_protect(cont_title, LV_PROTECT_PRESS_LOST); /*slide down for list from title*/

    lv_obj_t * cont_name = lv_cont_create(cont_title, NULL);
    lv_obj_set_size(cont_name, width_obj * 5 / 8 + 2, height_label_cont);
    lv_obj_add_style(cont_name, LV_CONT_PART_MAIN, &lv_style_transp_tight);
    lv_obj_align(cont_name, cont_title, LV_ALIGN_CENTER, 0, 2);
    lv_obj_set_click(cont_name, false);

    lv_obj_t * label_name = lv_label_create(cont_name, NULL);
    lv_obj_set_style_local_text_font(label_name,
                                     LV_LABEL_PART_MAIN,
                                     LV_STATE_DEFAULT,
                                     LV_THEME_WATCH_NIGHT_FONT_SMALL);
    lv_obj_set_style_local_text_color(label_name,
                                      LV_LABEL_PART_MAIN,
                                      LV_STATE_DEFAULT,
                                      LV_COLOR_MAKE(16, 112, 194));
    lv_label_set_long_mode(label_name, LV_LABEL_LONG_SROLL);
    lv_obj_set_width(label_name, lv_obj_get_width(cont_name));
    lv_label_set_align(label_name, LV_LABEL_ALIGN_CENTER);
    lv_label_set_text(label_name, text);
    lv_obj_align(label_name, cont_name, LV_ALIGN_CENTER, 0, 0);

    lv_obj_t * img_back = lv_img_create(cont_title, NULL);
    lv_img_set_src(img_back, ICON_BACK_BLUE);
    if(LV_BIDI_DIR_RTL == lv_obj_get_base_dir(cont)) {
        lv_img_set_angle(img_back, 1800);
        lv_obj_align(img_back, cont_title, LV_ALIGN_IN_RIGHT_MID, 0, 2);
    } else {
        lv_obj_align(img_back, cont_title, LV_ALIGN_IN_LEFT_MID, 0, 2);
    }
    lv_obj_set_click(img_back, true);
    lv_obj_set_event_cb(img_back, voice_msg_title_img_back_event_cb);
    lv_obj_set_adv_hittest(img_back, false);
    lv_watch_obj_add_element(img_back);
    lv_obj_clear_protect(img_back, LV_PROTECT_PRESS_LOST); /*slide down for list from title*/

    return cont_title;
}

static char * voice_msg_display_msg_time(lv_bidi_dir_t dir,
                                         hal_rtc_t * msg_time,
                                         hal_rtc_t * cur_time,
                                         hal_rtc_t * last_msg_time)
{
    char * time = NULL;
    if(0 == last_msg_time->tm_year) {
        time = (char *)lv_mem_alloc(17); /*for example 2015/12/14 20:15*/
        memset(time, 0, 17);
        if(LV_BIDI_DIR_RTL == dir) {
            snprintf(time, 17, "%02d:%02d %d/%d/%d", msg_time->tm_hour, msg_time->tm_min,
                     msg_time->tm_mon, msg_time->tm_mday, msg_time->tm_year);
        } else {
            snprintf(time, 17, "%d/%d/%d %02d:%02d", msg_time->tm_year, msg_time->tm_mon,
                     msg_time->tm_mday, msg_time->tm_hour, msg_time->tm_min);
        }
        return time;
    }

    uint32_t msg_sec = time_to_seconds(msg_time);
    uint32_t last_msg_sec = time_to_seconds(last_msg_time);
    if(msg_sec < last_msg_sec) {
        time = (char *)lv_mem_alloc(17); /*for example 2015/12/14 20:15*/
        memset(time, 0, 17);
        if(LV_BIDI_DIR_RTL == dir) {
            snprintf(time, 17, "%02d:%02d %d/%d/%d", msg_time->tm_hour, msg_time->tm_min,
                     msg_time->tm_mon, msg_time->tm_mday, msg_time->tm_year);
        } else {
            snprintf(time, 17, "%d/%d/%d %02d:%02d", msg_time->tm_year, msg_time->tm_mon,
                     msg_time->tm_mday, msg_time->tm_hour, msg_time->tm_min);
        }
    } else if(msg_sec > (last_msg_sec + VOICE_MSG_CHAT_TIME_INTERVAL)) {
        uint32_t cur_sec = time_to_seconds(cur_time);
        if((msg_sec > cur_sec)
           || (msg_time->tm_year < cur_time->tm_year)) {
            time = (char *)lv_mem_alloc(17); /*for example 2015/12/14 20:15*/
            memset(time, 0, 17);
            if(LV_BIDI_DIR_RTL == dir) {
                snprintf(time, 17, "%02d:%02d %d/%d/%d", msg_time->tm_hour, msg_time->tm_min,
                         msg_time->tm_mon, msg_time->tm_mday, msg_time->tm_year);
            } else {
                snprintf(time, 17, "%d/%d/%d %02d:%02d", msg_time->tm_year, msg_time->tm_mon,
                         msg_time->tm_mday, msg_time->tm_hour, msg_time->tm_min);
            }
        } else {
            uint32_t today_sec = cur_sec
                - cur_time->tm_hour * 3600
                - cur_time->tm_min * 60
                - cur_time->tm_sec;
            if(msg_sec >= today_sec) {
                /*today*/
                time = (char *)lv_mem_alloc(6); /*for example 20:15*/
                memset(time, 0, 6);
                snprintf(time, 6, "%02d:%02d", msg_time->tm_hour, msg_time->tm_min);
            } else if(msg_sec >= (today_sec - 24 * 3600)) {
                /*yesterday*/
                const char * yesterday = lv_lang_get_text(WATCH_TEXT_ID_YESTERDAY);
                uint8_t len = strlen(yesterday) + 7;
                time = (char *)lv_mem_alloc(len); /*for example yesterday 20:15*/
                memset(time, 0, len);
                if(LV_BIDI_DIR_RTL == dir) {
                    snprintf(time, len, "%02d:%02d %s", msg_time->tm_hour,
                             msg_time->tm_min, yesterday);
                } else {
                    snprintf(time, len, "%s %02d:%02d", yesterday, msg_time->tm_hour,
                             msg_time->tm_min);
                }
            } else {
                uint8_t wday;
                if(0 == cur_time->tm_wday) {
                    /*today is Sunday*/
                    wday = 6;
                } else {
                    wday = cur_time->tm_wday - 1;
                }
                /*the start of a week is monday, the end is sunday*/
                if(msg_sec >= (today_sec - wday * 24 * 3600)) {
                    /*current week before yesterday*/
                    const char * wday_str;
                    switch(rtc_calc_weekday(msg_time)) {
                    case 1:
                        /*Monday*/
                        wday_str = lv_lang_get_text(WATCH_TEXT_ID_MONDAY);
                        break;
                    case 2:
                        /*Tuesday*/
                        wday_str = lv_lang_get_text(WATCH_TEXT_ID_TUESDAY);
                        break;
                    case 3:
                        /*Wednesday*/
                        wday_str = lv_lang_get_text(WATCH_TEXT_ID_WEDNESDAY);
                        break;
                    case 4:
                        /*Thursday*/
                        wday_str = lv_lang_get_text(WATCH_TEXT_ID_THURSDAY);
                        break;
                    case 5:
                        /*Friday*/
                        wday_str = lv_lang_get_text(WATCH_TEXT_ID_FRIDAY);
                        break;
                    default:
                        printf("%s: week error\n", __FUNCTION__);
                        break;
                    }
                    uint8_t len = strlen(wday_str) + 7;
                    time = (char *)lv_mem_alloc(len); /*for example Monday 20:15*/
                    memset(time, 0, len);
                    if(LV_BIDI_DIR_RTL == dir) {
                        snprintf(time, len, "%02d:%02d %s", msg_time->tm_hour,
                                 msg_time->tm_min, wday_str);
                    } else {
                        snprintf(time, len, "%s %02d:%02d", wday_str, msg_time->tm_hour,
                                 msg_time->tm_min);
                    }
                } else {
                    time = (char *)lv_mem_alloc(12); /*for example 11/14 20:15*/
                    memset(time, 0, 12);
                    if(LV_BIDI_DIR_RTL == dir) {
                        snprintf(time, 12, "%02d:%02d %d/%d", msg_time->tm_hour, msg_time->tm_min,
                                 msg_time->tm_mon, msg_time->tm_mday);
                    } else {
                        snprintf(time, 12, "%d/%d %02d:%02d", msg_time->tm_mon, msg_time->tm_mday,
                                 msg_time->tm_hour, msg_time->tm_min);
                    }
                }
            }
        }
    }

    return time;
}

static bool voice_msg_is_group_msg(watch_app_adp_voice_msg_chat_type_t chat_type, char * name)
{
    bool group_msg = false;
    if((WATCH_VOICE_MSG_FAMILY_GROUP_CHAT == chat_type)
            || (WATCH_VOICE_MSG_FRIENDS_GROUP_CHAT == chat_type)) {
        group_msg = true;
    } else if((WATCH_VOICE_MSG_INVALID_CHAT == chat_type) && (NULL != name)) {
        const char * family_group = lv_lang_get_text(WATCH_TEXT_ID_FAMILY_GROUP);
        const char * friends_group = lv_lang_get_text(WATCH_TEXT_ID_FRIENDS_GROUP);
        if((0 == strcmp(name, family_group))
                || (0 == strcmp(name, friends_group))) {
            group_msg = true;
        }

    }
    printf("%s: group msg %d\n", __FUNCTION__, group_msg);

    return group_msg;
}

static lv_obj_t * voice_msg_display_msg_to_chat(lv_obj_t * list,
                                                app_adaptor_voice_msg_t * msgs,
                                                hal_rtc_t * last_msg_time)
{
    printf("%s\n", __FUNCTION__);

    if(NULL == msgs) return NULL;
    if(NULL == last_msg_time) return NULL;

    voice_msg_desc_t msg_desc;
    char * contact_name = NULL;
    uint8_t i = 0;
    if(VOICE_MSG_MAX_CHAT_MSG_NUM < msgs->count) {
        i = msgs->count - VOICE_MSG_MAX_CHAT_MSG_NUM;
    }

    hal_rtc_t cur_time;
    Hal_Rtc_Gettime(&cur_time);

    for(; i < msgs->count; i++) {
        hal_rtc_t msg_time;
        msg_time.tm_year = msgs->msg[i].time.year;
        msg_time.tm_mon = msgs->msg[i].time.month;
        msg_time.tm_mday = msgs->msg[i].time.day;
        msg_time.tm_hour = msgs->msg[i].time.hour;
        msg_time.tm_min = msgs->msg[i].time.min;
        msg_time.tm_sec = 0;

        msg_desc.time_str = voice_msg_display_msg_time(lv_obj_get_base_dir(list),
                                                       &msg_time, &cur_time, last_msg_time);
        if(NULL != msg_desc.time_str) {
            msg_desc.type = VOICE_MSG_TYPE_TIME;
            voice_msg_list_add(list, &msg_desc);
            lv_mem_free(msg_desc.time_str);
        }

        last_msg_time->tm_year = msgs->msg[i].time.year;
        last_msg_time->tm_mon = msgs->msg[i].time.month;
        last_msg_time->tm_mday = msgs->msg[i].time.day;
        last_msg_time->tm_hour = msgs->msg[i].time.hour;
        last_msg_time->tm_min = msgs->msg[i].time.min;

        if(WATCH_VOICE_MSG_FROM_UI == msgs->msg[i].direction) {
            msg_desc.direction = 0;
        } else {
            msg_desc.direction = 1;
            if(true == voice_msg_is_group_msg(msgs->chat_type, msgs->name)) {
                if((NULL == contact_name)
                        || (VOICE_MSG_TYPE_TIME == msg_desc.type)
                        || ((NULL != contact_name)
                            && (0 != strcmp(contact_name, msgs->msg[i].name)))) {
                    /*display contact name*/
                    msg_desc.type = VOICE_MSG_TYPE_NAME;
                    msg_desc.name_str = msgs->msg[i].name;
                    voice_msg_list_add(list, &msg_desc);
                }
                contact_name = msgs->msg[i].name;
            }
        }
        msg_desc.size = 0;
        if(WATCH_VOICE_MSG_TYPE_TEXT == msgs->msg[i].type) {
            msg_desc.type = VOICE_MSG_TYPE_TEXT;
            if(WATCH_VOICE_DATA_TYPE_BUFFER == msgs->msg[i].data_type) {
                msg_desc.size = strlen(msgs->msg[i].content.text) + 1;
            }
            msg_desc.data = msgs->msg[i].content.text;
        } else if(WATCH_VOICE_MSG_TYPE_VOICE == msgs->msg[i].type) {
            msg_desc.type = VOICE_MSG_TYPE_VOICE;
            msg_desc.voice_index = msgs->msg[i].content.voice.index;
            if(true == msgs->msg[i].read_flag) {
                msg_desc.have_read = 1;
            } else {
                msg_desc.have_read = 0;
            }
            if(WATCH_VOICE_DATA_TYPE_FILE == msgs->msg[i].data_type) {
                /*file path string*/
                uint32_t size = strlen((char *)msgs->msg[i].content.voice.file) + 1;
                msg_desc.data = lv_mem_alloc(size);
                strncpy(msg_desc.data, (char *)msgs->msg[i].content.voice.file, size);
            } else {
                /*data buffer*/
                msg_desc.size = msgs->msg[i].content.voice.voice_len;
                msg_desc.data = lv_mem_alloc(msg_desc.size);
                memcpy(msg_desc.data, msgs->msg[i].content.voice.file, msg_desc.size);
            }
        } else if((WATCH_VOICE_MSG_TYPE_EXPRESSION == msgs->msg[i].type)
                  || (WATCH_VOICE_MSG_TYPE_PHOTO == msgs->msg[i].type)) {
            if(WATCH_VOICE_MSG_TYPE_EXPRESSION == msgs->msg[i].type) {
                msg_desc.type = VOICE_MSG_TYPE_EXPRESSION;
            } else {
                msg_desc.type = VOICE_MSG_TYPE_PHOTO;
            }
            if(WATCH_VOICE_DATA_TYPE_FILE == msgs->msg[i].data_type) {
                /*file path string*/
                msg_desc.data = msgs->msg[i].content.img.data;
            } else {
                /*data buffer*/
                msg_desc.size = msgs->msg[i].content.img.data_size;
                msg_desc.data = lv_mem_alloc(msg_desc.size);
                memcpy(msg_desc.data, msgs->msg[i].content.img.data, msg_desc.size);
            }
        }
        voice_msg_list_add(list, &msg_desc);
    }
    msg_desc.type = VOICE_MSG_TYPE_EMO_AND_TOUCH;
    lv_obj_t * cont = voice_msg_list_add(list, &msg_desc);

    return cont;
}

static void voice_msg_free_msgs(app_adaptor_voice_msg_t * msgs)
{
    if(NULL == msgs) return;
    if(msgs->name) lv_mem_free(msgs->name);
    if(msgs->number) lv_mem_free(msgs->number);
    if(NULL == msgs->msg) return;
    for(uint8_t i = 0; i < msgs->count; i++) {
        if(msgs->msg[i].name) lv_mem_free(msgs->msg[i].name);
        if(WATCH_VOICE_MSG_TYPE_TEXT == msgs->msg[i].type)
            lv_mem_free(msgs->msg[i].content.text);
        else if(WATCH_VOICE_MSG_TYPE_VOICE == msgs->msg[i].type)
            lv_mem_free(msgs->msg[i].content.voice.file);
        else if((WATCH_VOICE_MSG_TYPE_EXPRESSION == msgs->msg[i].type)
                || (WATCH_VOICE_MSG_TYPE_PHOTO == msgs->msg[i].type))
            lv_mem_free(msgs->msg[i].content.img.data);
    }
    lv_mem_free(msgs->msg);
    lv_mem_free(msgs);
}

static void voice_msg_chat_free_mem(lv_voice_msg_chat_ext_t * ext)
{
    if(NULL == ext) return;
    if(NULL == ext->list) return;
    lv_obj_t * scrl = lv_page_get_scrl(ext->list);
    lv_obj_t * child = lv_obj_get_child_back(scrl, NULL);
    while(child) {
        voice_msg_type_t type = VOICE_MSG_GET_MSG_TYPE(voice_msg_get_user_num(child));
        if((VOICE_MSG_TYPE_EXPRESSION == type) || (VOICE_MSG_TYPE_PHOTO == type)) {
            const void * src = lv_img_get_src(child);
            if(LV_IMG_SRC_VARIABLE == lv_img_src_get_type(src)) {
                lv_img_dsc_t * dsc = (lv_img_dsc_t *)src;
                lv_mem_free(dsc->data);
                lv_mem_free(dsc);
            }
        } else if(VOICE_MSG_TYPE_VOICE == type) {
            lv_obj_t * len_label = lv_obj_get_child(child, NULL);
            void * ptr = voice_msg_get_user_ptr(len_label);
            if(ptr) lv_mem_free(ptr);
        }
        child = lv_obj_get_child_back(scrl, child);
    }
}

static void voice_msg_chat_prepare_destory(lv_obj_t * activity_obj)
{
    printf("%s\n", __FUNCTION__);

    lv_voice_msg_chat_ext_t * chat_ext = voice_msg_get_ext(ACT_ID_VOICE_MSG_CHAT);
    if(NULL == chat_ext) return;

    if(chat_ext->list) {
        lv_voice_msg_list_ext_t * list_ext = lv_obj_get_ext_attr(chat_ext->list);
        if(NULL == list_ext) return;
        lv_style_reset(&(list_ext->style_cont));
        lv_style_reset(&(list_ext->style_label_time));
        lv_style_reset(&(list_ext->style_text));
        lv_style_reset(&(list_ext->style_label_len_other));
        lv_style_reset(&(list_ext->style_label_len_ui));
    }

    voice_msg_chat_stop_voice(chat_ext);
    voice_msg_chat_free_mem(chat_ext);
    lv_watch_png_cache_all_free();
    if(chat_ext->contact_list_update) {
        printf("%s: update contact list\n", __FUNCTION__);
        voice_msg_update_contact_list(voice_msg_get_ext(ACT_ID_VOICE_MSG));
    }
}

static void voice_msg_create_chat(lv_ll_t * contact_list, voice_msg_contact_t * contact)
{
    if(NULL == contact_list) return;
    if(NULL == contact) return;

    printf("%s: chat with %s\n", __FUNCTION__, contact->name);

    lv_watch_activity_ext_t activity_ext;
    memset(&activity_ext, 0, sizeof(lv_watch_activity_ext_t));
    activity_ext.actId = ACT_ID_VOICE_MSG_CHAT;
    activity_ext.create = NULL;
    activity_ext.prepare_destory = voice_msg_chat_prepare_destory;
    lv_obj_t * activity_obj = lv_watch_creat_activity_obj(&activity_ext);
    LV_ASSERT_MEM(activity_obj);

    lv_obj_t * obj = lv_watch_obj_create(activity_obj);
    LV_ASSERT_MEM(obj);
    lv_voice_msg_chat_ext_t * ext = lv_obj_allocate_ext_attr(obj, sizeof(lv_voice_msg_chat_ext_t));
    LV_ASSERT_MEM(ext);
    ext->list = NULL;
    memset(&ext->last_msg_time, 0, sizeof(hal_rtc_t));
    ext->contact_list_update = false;
    ext->contact_list = contact_list;
    ext->cur_contact = contact;
    ext->label_no_msg = NULL;
    ext->img_emo = NULL;
    ext->img_touch = NULL;
    ext->buffer = NULL;

    lv_obj_t * cont = lv_cont_create(obj, NULL);
    lv_obj_set_size(cont, lv_obj_get_width(obj), lv_obj_get_height(obj));
    lv_obj_align(cont, obj, LV_ALIGN_CENTER, 0, 0);
    lv_obj_set_style_local_bg_color(cont,
                                    LV_CONT_PART_MAIN,
                                    LV_STATE_DEFAULT,
                                    LV_COLOR_MAKE(194, 252, 252));
    lv_watch_obj_add_element(cont);

    ext->cont_title = voice_msg_create_title(cont, contact->name);

    app_adaptor_voice_msg_chat_id_t * id = (app_adaptor_voice_msg_chat_id_t *)lv_mem_alloc(sizeof(app_adaptor_voice_msg_chat_id_t));
    voice_msg_fill_chat_id(id, contact);
    app_adaptor_voice_msg_t * msgs = app_adaptor_voice_msg_get_msgs_req(id);
    uint8_t msg_count = 0;
    if(NULL != msgs) {
        msg_count = msgs->count;
        if(VOICE_MSG_MAX_CHAT_MSG_NUM < msgs->count) {
            printf("%s: msgs->count %d exceeds max num\n", __FUNCTION__, msgs->count);
            msg_count = VOICE_MSG_MAX_CHAT_MSG_NUM;
        }
        if(0 < msgs->count) {
            lv_coord_t list_height = lv_obj_get_height(cont) - lv_obj_get_height(ext->cont_title) - 4;
            lv_obj_t * list = voice_msg_list_create(cont, lv_obj_get_width(cont), list_height);
            ext->list = list;
            lv_obj_align(list, ext->cont_title, LV_ALIGN_OUT_BOTTOM_LEFT, 0, 0);
            lv_coord_t scrl_scrn_height = list_height
                - lv_obj_get_style_pad_top(list, LV_LIST_PART_BG) * 2;
            lv_obj_t * btn = voice_msg_display_msg_to_chat(list, msgs, &ext->last_msg_time);
            lv_obj_t * scrl = lv_page_get_scrl(list);
            lv_coord_t scrl_h = lv_obj_get_height(scrl);
            if(scrl_scrn_height > scrl_h) {
                /*set emo and touch img to bottow of chat window*/
                lv_cont_set_layout(scrl, LV_LAYOUT_OFF);
                lv_coord_t btn_y = scrl_scrn_height - lv_obj_get_height(btn);
                lv_obj_set_y(btn, btn_y);
            } else
                lv_list_focus(btn, false);
        }

        voice_msg_free_msgs(msgs);
    }

    if(0 == msg_count) {
        ext->label_no_msg = lv_label_create(cont, NULL);
        lv_obj_add_style(ext->label_no_msg, LV_LABEL_PART_MAIN, &lv_watch_font20_black);
        lv_label_set_text_id(ext->label_no_msg, WATCH_TEXT_ID_NO_VOICE_MSG);
        lv_obj_align(ext->label_no_msg, cont, LV_ALIGN_CENTER, 0, -10);

        ext->img_emo = lv_img_create(cont, NULL);
        lv_img_set_src(ext->img_emo, ICON_SEND_EMO);
        lv_obj_set_click(ext->img_emo, true);
        lv_obj_set_event_cb(ext->img_emo, voice_msg_send_emo_event_cb);
        lv_obj_set_adv_hittest(ext->img_emo, false);
        lv_watch_obj_add_element(ext->img_emo);

        ext->img_touch = lv_img_create(cont, NULL);
        lv_img_set_src(ext->img_touch, ICON_TOUCH_SPK);
        lv_obj_set_click(ext->img_touch, true);
        lv_obj_set_event_cb(ext->img_touch, voice_msg_touch_spk_event_cb);
        lv_obj_set_adv_hittest(ext->img_touch, false);
        lv_watch_obj_add_element(ext->img_touch);

        if(LV_BIDI_DIR_RTL == lv_obj_get_base_dir(obj)){
            lv_obj_align(ext->img_touch, cont, LV_ALIGN_IN_BOTTOM_LEFT, 14, -8);
            lv_obj_align(ext->img_emo, cont, LV_ALIGN_IN_BOTTOM_RIGHT, -14, -8);
        } else {
            lv_obj_align(ext->img_emo, cont, LV_ALIGN_IN_BOTTOM_LEFT, 14, -8);
            lv_obj_align(ext->img_touch, cont, LV_ALIGN_IN_BOTTOM_RIGHT, -14, -8);
        }
    }

    ext->msg_count = msg_count;
}

static void voice_msg_img_event_cb(lv_obj_t * img, lv_event_t e)
{
    lv_voice_msg_ext_t * ext = voice_msg_get_ext(ACT_ID_VOICE_MSG);
    if(NULL == ext) return;

    if(LV_EVENT_PRESSED == e) {
        ext->pressing = true;
    } else if((true == ext->pressing) && (LV_EVENT_CLICKED == e)) {
        ext->pressing = false;
        /*click the contact*/
        lv_obj_t * img_portrait = lv_obj_get_child_back(img, NULL);
        voice_msg_contact_t * contact = voice_msg_get_user_ptr(img_portrait);
        if((0xFF != contact->mark) && (0 < contact->mark)) {
            contact->mark = 0;
            lv_obj_t * cont_mark = lv_obj_get_child_back(img, img_portrait);
            if(NULL == lv_obj_get_child_back(img, cont_mark)) {
                printf("%s: warning, cont_mark is NULL\n", __FUNCTION__);
            } else {
                lv_obj_del(cont_mark);
            }
        }
        voice_msg_create_chat(ext->contact_list, contact);
    }
}

static lv_res_t voice_msg_page_scrl_signal(lv_obj_t * page_scrl, lv_signal_t sign, void * param)
{
    lv_res_t res;

    lv_voice_msg_ext_t * ext = voice_msg_get_ext(ACT_ID_VOICE_MSG);
    if(NULL == ext) return LV_RES_INV;

    /* Include the ancient signal function */
    res = ext->ancient_page_scrl_signal(page_scrl, sign, param);
    if(res != LV_RES_OK) return res;

    if(LV_SIGNAL_COORD_CHG == sign) {
        if(true == ext->pressing) {
            ext->pressing = false;
        }
    } else if(LV_SIGNAL_DRAG_END == sign) {
        lv_coord_t init_y = lv_obj_get_style_pad_top(lv_obj_get_parent(page_scrl),
                                                     LV_PAGE_PART_BG);
        lv_coord_t y = lv_obj_get_y(page_scrl);
        if(init_y < y) {
            return LV_RES_INV;
        } else if(init_y == y) {
            return LV_RES_OK;
        }

        lv_coord_t top = lv_obj_get_style_pad_top(lv_obj_get_parent(page_scrl),
                                                  LV_PAGE_PART_SCROLLABLE);
        if((0 > y) && (-y > (top + ext->img_height / 2))) {
            y = -y - top;
            lv_coord_t inner = lv_obj_get_style_pad_inner(lv_obj_get_parent(page_scrl),
                                                          LV_PAGE_PART_SCROLLABLE);
            lv_coord_t move_height = ext->img_height + inner;
            uint8_t i = y / move_height;
            y -= i * move_height;
            lv_coord_t aligned_y;
            if(ext->img_height / 2 < y) {
                aligned_y = init_y - (top + (i + 1) * move_height);
            } else {
                aligned_y = init_y - (top + i * move_height);
            }
            lv_obj_set_y(page_scrl, aligned_y);
        } else {
            lv_obj_set_y(page_scrl, init_y);
        }
    }

    return LV_RES_OK;
}

static lv_obj_t * voice_msg_add_contact(lv_voice_msg_ext_t * ext, voice_msg_contact_t * contact)
{
    if(NULL == ext) return NULL;
    if(NULL == contact) return NULL;

    lv_bidi_dir_t dir = lv_obj_get_base_dir(ext->page);

    lv_obj_t * img_bkg = lv_img_create(ext->page, NULL);
    lv_img_set_src(img_bkg, ICON_BKG);
    lv_obj_set_click(img_bkg, true);
    lv_page_glue_obj(img_bkg, true);
    if(VOICE_MSG_FROM_SELF == ext->other_app) {
        lv_obj_set_event_cb(img_bkg, voice_msg_img_event_cb);
    } else if(VOICE_MSG_FROM_ALBUM == ext->other_app){
        lv_obj_set_event_cb(img_bkg, album_photo_share_event_cb);
    }  else if(VOICE_MSG_FROM_IMGVIEWER == ext->other_app){
#if USE_LV_WATCH_IMGVIEWER
        lv_obj_set_event_cb(img_bkg, imgviewer_photo_share_event_cb);
#endif
    }
    lv_obj_set_adv_hittest(img_bkg, false);
    lv_obj_add_protect(img_bkg, LV_PROTECT_PRESS_LOST);
    lv_watch_obj_add_element(img_bkg);

    lv_obj_t * img_portrait = lv_img_create(img_bkg, NULL);
    lv_img_set_src(img_portrait, voice_msg_get_img_src(contact->portrait_id));
    lv_coord_t space = 2;
    lv_coord_t zoom_h = lv_obj_get_height(img_bkg) - 2 * space;
    lv_coord_t img_portrait_w = lv_obj_get_width(img_portrait);
    lv_coord_t img_portrait_h = lv_obj_get_height(img_portrait);
    lv_coord_t zoom_w = img_portrait_w * zoom_h / img_portrait_h;
    lv_img_set_zoom(img_portrait, LV_IMG_ZOOM_NONE * zoom_h / img_portrait_h);
    if(LV_BIDI_DIR_RTL == dir) {
        lv_obj_align(img_portrait, img_bkg, LV_ALIGN_IN_RIGHT_MID,
                     - space - (zoom_w - img_portrait_w) / 2, 0);
    } else {
        lv_obj_align(img_portrait, img_bkg, LV_ALIGN_IN_LEFT_MID,
                     space + (zoom_w - img_portrait_w) / 2, 0);
    }
    voice_msg_set_user_ptr(img_portrait, contact);

    if((0xFF != contact->mark) && (0 < contact->mark)) {
        lv_obj_t * cont_mark = lv_cont_create(img_bkg, NULL);
        lv_obj_set_click(cont_mark, false);
        lv_obj_t * label_mark = lv_label_create(cont_mark, NULL);
        lv_obj_add_style(label_mark, LV_LABEL_PART_MAIN, &ext->style_label_mark);
        lv_coord_t h_font
            = lv_font_get_line_height(lv_obj_get_style_text_font(label_mark, LV_LABEL_PART_MAIN))
            + 2;
        if(10 > contact->mark) {
            lv_obj_set_size(cont_mark, h_font, h_font);
        } else if(100 > contact->mark) {
            lv_obj_set_size(cont_mark, h_font * 3 / 2, h_font);
        } else {
            lv_obj_set_size(cont_mark, h_font * 2, h_font);
        }
        if(LV_BIDI_DIR_RTL == dir) {
            lv_obj_align(cont_mark, img_bkg, LV_ALIGN_IN_TOP_RIGHT,
                         - space - zoom_w + lv_obj_get_width(cont_mark), space);
        } else {
            lv_obj_align(cont_mark, img_bkg, LV_ALIGN_IN_TOP_LEFT,
                         space + zoom_w - lv_obj_get_width(cont_mark), space);
        }
        lv_obj_add_style(cont_mark, LV_CONT_PART_MAIN, &ext->style_cont_mark);

        char text[4] = {0};
        snprintf(text, 4, "%d", contact->mark);
        lv_label_set_text(label_mark, text);
        lv_obj_align(label_mark, cont_mark, LV_ALIGN_CENTER, 0, 1);
    }

    lv_obj_t * label = lv_label_create(img_bkg, NULL);
    lv_obj_add_style(label, LV_LABEL_PART_MAIN, &lv_watch_font30_black);
    lv_label_set_long_mode(label, LV_LABEL_LONG_SROLL_CIRC);
    lv_coord_t inner = 8;
    lv_obj_set_width(label, lv_obj_get_width(img_bkg) - 2 * space - zoom_w - inner);
    lv_label_set_text(label, contact->name);
    if(LV_BIDI_DIR_RTL == dir) {
        lv_obj_align(label, img_bkg, LV_ALIGN_IN_LEFT_MID, inner, 0);
    } else {
        lv_obj_align(label, img_bkg, LV_ALIGN_IN_RIGHT_MID, -inner, 0);
    }

    return img_bkg;
}

static void voice_msg_contact_list_create(lv_voice_msg_ext_t * ext, lv_coord_t pos)
{
    lv_page_set_scrollable_fit2(ext->page, LV_FIT_NONE, LV_FIT_TIGHT);
    lv_obj_t * img;
    voice_msg_contact_t * contact = _lv_ll_get_head(ext->contact_list);
    while(NULL != contact) {
        img = voice_msg_add_contact(ext, contact);
        contact = _lv_ll_get_next(ext->contact_list, contact);
    }

    ext->img_height = lv_obj_get_height(img);

    // add blank at the end of page scrl
    lv_coord_t move_height = ext->img_height + lv_obj_get_style_pad_inner(ext->page, LV_PAGE_PART_SCROLLABLE);
    lv_obj_t * page_scrl = lv_page_get_scrl(ext->page);
    lv_coord_t height_scrl = lv_obj_get_height(page_scrl) + lv_obj_get_height(ext->page) - 2 * move_height;
    lv_page_set_scrollable_fit(ext->page, false);
    lv_obj_set_height(page_scrl, height_scrl);
    lv_obj_set_y(page_scrl, pos);
}

static void voice_msg_contacts_create(lv_obj_t * obj)
{
    lv_voice_msg_ext_t * ext = lv_obj_get_ext_attr(obj);
    if(NULL == ext) return;

    lv_coord_t width_obj = lv_obj_get_width(obj);
    lv_obj_t * content = lv_cont_create(obj, NULL);
    LV_ASSERT_MEM(content);
    lv_obj_set_size(content, width_obj, lv_obj_get_height(obj));
    lv_cont_set_layout(content, LV_LAYOUT_COLUMN_MID);
    lv_obj_set_style_local_bg_color(content,
                                    LV_CONT_PART_MAIN,
                                    LV_STATE_DEFAULT,
                                    LV_COLOR_MAKE(47, 233, 248));
    lv_obj_set_click(content, false);
    lv_obj_align(content, obj, LV_ALIGN_CENTER, 0, 0);

    lv_obj_t * label_cont;
    if(VOICE_MSG_FROM_SELF != ext->other_app)
        label_cont = voice_msg_create_title(content,
                                            (char *)(lv_lang_get_text(WATCH_TEXT_ID_CHOOSE_CONTACT)));
    else
        label_cont = voice_msg_create_title(content,
                                            (char *)(lv_lang_get_text(WATCH_TEXT_ID_SEND_MSG)));

    ext->page = lv_page_create(content, NULL);
    LV_ASSERT_MEM(ext->page);
    lv_coord_t height_page = lv_obj_get_height(content) - lv_obj_get_height(label_cont);
    lv_obj_set_size(ext->page, lv_obj_get_width(content), height_page);
    lv_page_set_scrollbar_mode(ext->page, LV_SCROLLBAR_MODE_OFF);
    lv_obj_add_style(ext->page, LV_PAGE_PART_BG, &lv_watch_style_transp);
    lv_obj_set_style_local_bg_opa(ext->page,
                                  LV_PAGE_PART_SCROLLABLE,
                                  LV_STATE_DEFAULT,
                                  LV_OPA_0);
    lv_obj_set_style_local_pad_top(ext->page,
                                   LV_PAGE_PART_SCROLLABLE,
                                   LV_STATE_DEFAULT,
                                   0);
    lv_obj_set_style_local_pad_bottom(ext->page,
                                      LV_PAGE_PART_SCROLLABLE,
                                      LV_STATE_DEFAULT,
                                      0);
    lv_obj_set_style_local_pad_inner(ext->page,
                                     LV_PAGE_PART_SCROLLABLE,
                                     LV_STATE_DEFAULT,
                                     8);
    lv_obj_set_style_local_bg_color(ext->page,
                                    LV_PAGE_PART_SCROLLBAR,
                                    LV_STATE_DEFAULT,
                                    LV_COLOR_MAKE(16, 112, 194));
    lv_obj_set_style_local_pad_inner(ext->page,
                                     LV_PAGE_PART_SCROLLBAR,
                                     LV_STATE_DEFAULT,
                                     LV_DPI / 16);
    lv_obj_set_style_local_radius(ext->page,
                                  LV_PAGE_PART_SCROLLBAR,
                                  LV_STATE_DEFAULT,
                                  0);
    lv_page_set_scrl_layout(ext->page, LV_LAYOUT_COLUMN_MID);
    lv_obj_t * page_scrl = lv_page_get_scrl(ext->page);
    ext->ancient_page_scrl_signal = lv_obj_get_signal_cb(page_scrl);
    lv_obj_set_signal_cb(page_scrl, voice_msg_page_scrl_signal);
    lv_watch_obj_add_element(ext->page);
    lv_watch_obj_add_element(page_scrl);

    voice_msg_contact_list_create(ext, 2);
}

static void voice_msg_img_close_event_cb(lv_obj_t * img, lv_event_t e)
{
    if(LV_EVENT_CLICKED == e) {
        voice_msg_del_top_interface();
    }
}

static void voice_msg_img_back_event_cb(lv_obj_t * img, lv_event_t e)
{
    if(LV_EVENT_CLICKED == e) {
        lv_obj_t * activity_obj = lv_watch_get_top_activity_obj();
        if(activity_obj == NULL) return;
        lv_watch_activity_ext_t * ext = lv_obj_get_ext_attr(activity_obj);
        if(NULL == ext) return;
        lv_obj_t * obj = NULL;
        lv_watch_get_child_obj(activity_obj, lv_watch_obj_signal, &obj);
        lv_voice_msg_incoming_ext_t * obj_ext = lv_obj_get_ext_attr(obj);
        if(NULL == obj_ext) return;

        lv_ll_t * contact_list = obj_ext->contact_list;
        obj_ext->contact_list = NULL;
        voice_msg_contact_t * cur_contact = obj_ext->cur_contact;
        if(cur_contact) {
            cur_contact->mark = 0;
        }

        if(ext->prepare_destory) {
            ext->prepare_destory(activity_obj);
        }
        lv_obj_del(activity_obj);

        obj = voice_msg_create(NULL, 0, contact_list);
        LV_ASSERT_MEM(obj);
        if(cur_contact) {
            voice_msg_create_chat(contact_list, cur_contact);
        } else {
            printf("contact has been deleted in %s\n", __FUNCTION__);
        }
    }
}

static void voice_msg_incoming_prepare_destory(lv_obj_t * activity_obj)
{
    printf("%s\n", __FUNCTION__);

    lv_voice_msg_incoming_ext_t * ext = voice_msg_get_ext(ACT_ID_VOICE_MSG_INCOMING);
    if(NULL == ext) return;
#if VOICE_MSG_ENABLE_ONE_UNREAD_MSG != 0
    if(true == ext->voice_playing) {
        voice_msg_play_end(ext->voice_size);
    }
    Hal_Audio_Manage_End_Ind(AUDIO_CTRL_PRIORITY_5);
#endif
    if(ext->contact_list) {
        voice_msg_write_nvm(ext->contact_list);
        _lv_ll_clear(ext->contact_list);
        lv_mem_free(ext->contact_list);
    }
#if VOICE_MSG_ENABLE_ONE_UNREAD_MSG != 0
    if(ext->voice_data) {
        lv_mem_free(ext->voice_data);
    } else if(ext->img_src) {
        lv_mem_free(ext->img_src->data);
        lv_mem_free(ext->img_src);
    }
#endif

    lv_watch_png_cache_all_free();

#if VOICE_MSG_TEST != 0
    /* voice_msg_clear_nvm_for_test(); */
    /* voice_msg_test_clear(); */
#endif
}

#if VOICE_MSG_ENABLE_ONE_UNREAD_MSG != 0
static void voice_msg_voice_over_cb(void * para)
{
    lv_voice_msg_incoming_ext_t * ext = voice_msg_get_ext(ACT_ID_VOICE_MSG_INCOMING);
    if(NULL == ext) return;

    ext->index = 0xFF;
    ext->voice_playing = false;
    lv_anim_del(ext->img_voice_anim, NULL);
    lv_img_set_src(ext->img_voice_anim, ICON_PLAYING3);
    voice_msg_set_user_num(ext->img_voice_anim, 0);
    Hal_Audio_Manage_End_Ind(AUDIO_CTRL_PRIORITY_5);
}

void voice_msg_one_unread_msg_anim_voice(lv_anim_t * a)
{
    lv_obj_t * img_voice_anim = a->var;
    uint32_t anim_step = voice_msg_get_user_num(img_voice_anim);
    if(3 == anim_step) {
        lv_img_set_src(img_voice_anim, ICON_PLAYING1);
        voice_msg_set_user_num(img_voice_anim, 1);
    } else if(2 == anim_step) {
        lv_img_set_src(img_voice_anim, ICON_PLAYING3);
        voice_msg_set_user_num(img_voice_anim, 3);
    } else if(1 == anim_step) {
        lv_img_set_src(img_voice_anim, ICON_PLAYING2);
        voice_msg_set_user_num(img_voice_anim, 2);
    }
}

static void voice_msg_one_unread_msg_voice_event_cb(lv_obj_t * img, lv_event_t e)
{
    if(LV_EVENT_CLICKED == e) {
        lv_voice_msg_incoming_ext_t * ext = voice_msg_get_ext(ACT_ID_VOICE_MSG_INCOMING);
        if(NULL == ext) return;
        LV_ASSERT_MEM(ext->voice_data);
        uint32_t anim_step = voice_msg_get_user_num(ext->img_voice_anim);
        if(0 == anim_step) {
            /*animation not started*/
            lv_anim_t a = {};
            lv_anim_init(&a);
            lv_anim_set_var(&a, ext->img_voice_anim);
            lv_anim_set_start_cb(&a, (lv_anim_ready_cb_t)voice_msg_one_unread_msg_anim_voice);
            lv_anim_set_repeat_count(&a, LV_ANIM_REPEAT_INFINITE);
            lv_anim_set_time(&a, 400);
            lv_anim_start(&a);
            voice_msg_set_user_num(ext->img_voice_anim, 3);

            if(0xFF != ext->index) {
                app_adaptor_voice_msg_chat_id_t * id = (app_adaptor_voice_msg_chat_id_t *)lv_mem_alloc(sizeof(app_adaptor_voice_msg_chat_id_t));
                voice_msg_fill_chat_id(id, ext->cur_contact);
                app_adaptor_voice_msg_read_voice_req(id, ext->index);
                ext->index = 0xFF;
            }
        } else {
            voice_msg_play_end(ext->voice_size);
        }
        Hal_Audio_Manage_Start_Req(AUDIO_CTRL_PRIORITY_5,voice_msg_audio_ctrl_callback);
        Voice_Msg_Play_Onetime(ext->voice_data,
                               ext->voice_size,
                               query_current_volume(),
                               voice_msg_voice_over_cb,
                               NULL);

        ext->voice_playing = true;
    }
}

static void voice_msg_create_one_unread_msg(app_adaptor_voice_msg_t * info,
        lv_ll_t * contact_list,
        voice_msg_contact_t * contact)
{
    printf("%s: contact mark %d\n", __FUNCTION__, contact->mark);

    lv_watch_activity_ext_t activity_ext;
    memset(&activity_ext, 0, sizeof(lv_watch_activity_ext_t));
    activity_ext.actId = ACT_ID_VOICE_MSG_INCOMING;
    activity_ext.create = NULL;
    activity_ext.prepare_destory = voice_msg_incoming_prepare_destory;
    lv_obj_t * activity_obj = lv_watch_creat_activity_obj(&activity_ext);
    LV_ASSERT_MEM(activity_obj);

    lv_obj_t * obj = lv_watch_obj_create(activity_obj);
    LV_ASSERT_MEM(obj);
    lv_voice_msg_incoming_ext_t * ext = lv_obj_allocate_ext_attr(obj, sizeof(lv_voice_msg_incoming_ext_t));
    LV_ASSERT_MEM(ext);
    ext->voice_playing = false;
    ext->index = 0xFF;
    ext->contact_list = contact_list;
    ext->cur_contact = contact;
    ext->voice_size = 0;
    ext->voice_data = NULL;
    ext->img_src = NULL;
    ext->update = false;

    lv_coord_t width_obj = lv_obj_get_width(obj);
    lv_obj_t * cont = lv_cont_create(obj, NULL);
    lv_obj_set_size(cont, width_obj, lv_obj_get_height(obj));
    lv_obj_set_click(cont, false);
    lv_obj_align(cont, obj, LV_ALIGN_CENTER, 0, 0);
    lv_obj_set_style_local_bg_color(cont,
                                    LV_CONT_PART_MAIN,
                                    LV_STATE_DEFAULT,
                                    LV_COLOR_MAKE(194, 252, 252));

    lv_bidi_dir_t dir = lv_obj_get_base_dir(cont);

    lv_font_t * font = LV_THEME_WATCH_NIGHT_FONT_NORMAL;
    lv_coord_t font_h = lv_font_get_line_height(font);
    lv_obj_t * img_portrait = lv_img_create(cont, NULL);
    lv_img_set_src(img_portrait, voice_msg_get_img_src(contact->portrait_id));
    lv_coord_t inner = 8;
    lv_coord_t zoom_h = font_h * 2 + inner;
    lv_coord_t img_portrait_w = lv_obj_get_width(img_portrait);
    lv_coord_t img_portrait_h = lv_obj_get_height(img_portrait);
    lv_img_set_zoom(img_portrait, LV_IMG_ZOOM_NONE * zoom_h / img_portrait_h);
    lv_coord_t hor = 5;
    lv_coord_t zoom_w = img_portrait_w * zoom_h / img_portrait_h;
    if(LV_BIDI_DIR_RTL == dir) {
        lv_obj_align(img_portrait, cont, LV_ALIGN_IN_TOP_RIGHT, - hor - (zoom_w - img_portrait_w) / 2,
                     inner + (zoom_h - img_portrait_h) / 2);
    } else {
        lv_obj_align(img_portrait, cont, LV_ALIGN_IN_TOP_LEFT, hor + (zoom_w - img_portrait_w) / 2,
                     inner + (zoom_h - img_portrait_h) / 2);
    }

    lv_coord_t width_name = width_obj - hor * 2 - zoom_h - inner;
    lv_obj_t * label_name = lv_label_create(cont, NULL);
    lv_obj_set_style_local_text_font(label_name, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, font);
    lv_obj_set_style_local_text_color(label_name, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT,
                                      LV_COLOR_MAKE(64, 225, 238));
    lv_label_set_long_mode(label_name, LV_LABEL_LONG_SROLL_CIRC);
    lv_obj_set_width(label_name, width_name);
    lv_label_set_text(label_name, contact->name);
    lv_coord_t offset_x = inner + (zoom_w - img_portrait_w) / 2;
    if((VOICE_MSG_PORTRAIT_ID_FAMILY_GROUP == contact->portrait_id)
       || (VOICE_MSG_PORTRAIT_ID_FRIENDS_GROUP == contact->portrait_id)) {
        lv_obj_t * label_child_name = lv_label_create(cont, label_name);
        lv_label_set_text(label_child_name, info->msg->name);

        if(LV_BIDI_DIR_RTL == dir) {
            lv_obj_align(label_name, img_portrait, LV_ALIGN_OUT_LEFT_MID, -offset_x,
                         -font_h / 2 - inner / 4);
            lv_obj_align(label_child_name, img_portrait, LV_ALIGN_OUT_LEFT_MID, -offset_x,
                         font_h / 2 + inner / 4);
        } else {
            lv_obj_align(label_name, img_portrait, LV_ALIGN_OUT_RIGHT_MID, offset_x,
                         -font_h / 2 - inner / 4);
            lv_obj_align(label_child_name, img_portrait, LV_ALIGN_OUT_RIGHT_MID, offset_x,
                         font_h / 2 + inner / 4);
        }
    } else {
        if(LV_BIDI_DIR_RTL == dir) {
            lv_obj_align(label_name, img_portrait, LV_ALIGN_OUT_LEFT_MID, -offset_x, 0);
        } else {
            lv_obj_align(label_name, img_portrait, LV_ALIGN_OUT_RIGHT_MID, offset_x, 0);
        }
    }

    lv_obj_t * img_close = lv_img_create(cont, NULL);
    lv_img_set_src(img_close, ICON_CLOSE);
    lv_obj_set_click(img_close, true);
    lv_obj_set_event_cb(img_close, voice_msg_img_close_event_cb);
    lv_obj_set_adv_hittest(img_close, false);
    lv_watch_obj_add_element(img_close);

    lv_obj_t * img_back = lv_img_create(cont, NULL);
    lv_img_set_src(img_back, ICON_BACK);
    lv_obj_set_click(img_back, true);
    lv_obj_set_event_cb(img_back, voice_msg_img_back_event_cb);
    lv_obj_set_adv_hittest(img_back, false);
    lv_watch_obj_add_element(img_back);

    if(LV_BIDI_DIR_RTL == dir) {
        lv_obj_align(img_back, cont, LV_ALIGN_IN_BOTTOM_LEFT, 5, -8);
        lv_obj_align(img_close, cont, LV_ALIGN_IN_BOTTOM_RIGHT, -5, -8);
    } else {
        lv_obj_align(img_close, cont, LV_ALIGN_IN_BOTTOM_LEFT, 5, -8);
        lv_obj_align(img_back, cont, LV_ALIGN_IN_BOTTOM_RIGHT, -5, -8);
    }

    if(WATCH_VOICE_MSG_TYPE_TEXT == info->msg->type) {
        lv_obj_t * img_text = lv_img_create(cont, NULL);
        lv_img_set_src(img_text, ICON_TEXT_BKG);
        lv_obj_align(img_text, cont, LV_ALIGN_CENTER, 0, 0);

        lv_obj_t * label_text = lv_label_create(img_text, NULL);
        lv_obj_add_style(label_text, LV_LABEL_PART_MAIN, &lv_watch_font20_black);
        lv_label_set_long_mode(label_text, LV_LABEL_LONG_BREAK);
        lv_coord_t width_label_text = lv_obj_get_width(img_text) - 20;
        lv_coord_t height_label_text = 3 * lv_font_get_line_height(LV_THEME_WATCH_NIGHT_FONT_SMALL) + 4;
        lv_obj_set_size(label_text, width_label_text, height_label_text);
        lv_coord_t y_lable_text = (lv_obj_get_height(img_text) - height_label_text) / 2;
        lv_obj_align(label_text, img_text, LV_ALIGN_IN_TOP_LEFT, 10, y_lable_text);
        char * text = NULL;
        if(WATCH_VOICE_DATA_TYPE_FILE == info->msg->data_type) {
            /*file path string*/
            text = voice_msg_read_file_data(info->msg->content.text, VOICE_MSG_TYPE_TEXT, NULL);
            lv_label_set_text(label_text, text);
            lv_mem_free(text);
        } else {
            /*text string*/
            lv_label_set_text(label_text, info->msg->content.text);
        }
    } else if(WATCH_VOICE_MSG_TYPE_VOICE == info->msg->type) {
        ext->index = info->msg->content.voice.index;
        lv_obj_t * img_voice = lv_img_create(cont, NULL);
        lv_img_set_src(img_voice, ICON_VOICE_BUBBLE);
        lv_obj_align(img_voice, cont, LV_ALIGN_IN_LEFT_MID, 20, 0);
        lv_obj_set_click(img_voice, true);
        lv_obj_set_event_cb(img_voice, voice_msg_one_unread_msg_voice_event_cb);
        lv_obj_set_adv_hittest(img_voice, false);
        lv_watch_obj_add_element(img_voice);

        ext->img_voice_anim = lv_img_create(img_voice, NULL);
        lv_img_set_src(ext->img_voice_anim, ICON_PLAYING3);
        lv_obj_align(ext->img_voice_anim, img_voice, LV_ALIGN_IN_RIGHT_MID, -16, 0);
        voice_msg_set_user_num(ext->img_voice_anim, 0);

        uint32_t voice_size;
        if(WATCH_VOICE_DATA_TYPE_FILE == info->msg->data_type) {
            /*file path string*/
            voice_size = voice_msg_read_file_size((char *)info->msg->content.voice.file);
            ext->voice_size = 0;
        } else {
            /*data buffer*/
            voice_size = info->msg->content.voice.voice_len;
            ext->voice_size = info->msg->content.voice.voice_len;
        }
        if(VOICE_MSG_AMR_HEAD_SIZE <= voice_size) {
            voice_size -= VOICE_MSG_AMR_HEAD_SIZE;
        }
        uint8_t sec = voice_size / HAL_AMR_BUFSIZE_PER_SECOND;
        if(voice_size % HAL_AMR_BUFSIZE_PER_SECOND) sec++;
        char time[4] = {0};
        snprintf(time, 4, "%dS", sec);
        lv_obj_t * label_time = lv_label_create(cont, NULL);
        lv_obj_set_style_local_text_font(label_time,
                                         LV_LABEL_PART_MAIN,
                                         LV_STATE_DEFAULT,
                                         LV_THEME_WATCH_NIGHT_FONT_SMALL);
        lv_obj_set_style_local_text_color(label_time,
                                          LV_LABEL_PART_MAIN,
                                          LV_STATE_DEFAULT,
                                          LV_COLOR_MAKE(209, 150, 255));
        lv_label_set_text(label_time, time);
        lv_obj_align(label_time, img_voice, LV_ALIGN_OUT_RIGHT_MID, 8, 0);
        ext->voice_data = info->msg->content.voice.file;
        info->msg->content.voice.file = NULL;
    } else if((WATCH_VOICE_MSG_TYPE_EXPRESSION == info->msg->type)
              || (WATCH_VOICE_MSG_TYPE_PHOTO == info->msg->type)) {
        if(NULL != info->msg->content.img.data) {
            lv_obj_t * img_picture = lv_img_create(cont, NULL);
            if(WATCH_VOICE_DATA_TYPE_FILE == info->msg->data_type) {
                ext->img_src = NULL;
                lv_img_set_src(img_picture, info->msg->content.img.data);
            } else {
                ext->img_src = (lv_img_dsc_t *)lv_mem_alloc(sizeof(lv_img_dsc_t));
                ext->img_src->header.always_zero = 0;
                if(WATCH_VOICE_MSG_TYPE_EXPRESSION == info->msg->type) {
                    ext->img_src->header.w = VOICE_MSG_EXPRESSION_WIDTH;
                    ext->img_src->header.h = VOICE_MSG_EXPRESSION_HEIGHT;
                    ext->img_src->header.cf = LV_IMG_CF_RAW_ALPHA;
                } else {
                    ext->img_src->header.w = VOICE_MSG_PHOTO_WIDTH;
                    ext->img_src->header.h = VOICE_MSG_PHOTO_HEIGHT;
                    ext->img_src->header.cf = LV_IMG_CF_RAW;
                }
                ext->img_src->data_size = info->msg->content.img.data_size;
                ext->img_src->data = info->msg->content.img.data;
                info->msg->content.img.data = NULL;
                lv_img_set_src(img_picture, ext->img_src);
            }
            lv_obj_align(img_picture, cont, LV_ALIGN_CENTER, 0, 0);
            lv_obj_set_click(img_picture, false);
        } else {
            lv_obj_t * ta_unsupport = lv_textarea_create(cont, NULL);
            lv_cont_set_fit2(ta_unsupport, LV_FIT_NONE, LV_FIT_TIGHT);
            lv_obj_set_width(ta_unsupport, lv_obj_get_width(cont) - 8);
            lv_obj_add_style(ta_unsupport, LV_TEXTAREA_PART_BG, &lv_style_pretty);
            lv_obj_set_style_local_bg_color(ta_unsupport, LV_TEXTAREA_PART_BG,
                                            LV_STATE_DEFAULT, LV_COLOR_WHITE);
            lv_textarea_set_text(ta_unsupport, lv_lang_get_text(WATCH_TEXT_ID_IMG_UNSUPP));
            lv_obj_align(ta_unsupport, cont,
                         LV_ALIGN_IN_TOP_MID,
                         0,
                         lv_obj_get_height(img_portrait) + 6);
        }
    }
}
#endif

static void voice_msg_set_multi_label(lv_obj_t * label, uint8_t unread_count)
{
    const char * new_msg = lv_lang_get_text(WATCH_TEXT_ID_MULTI_NEW_MSG);
    uint8_t len_new_msg = strlen(new_msg);
    uint8_t len_text = 1 + 3 + len_new_msg + 1 + 1;
    char * text = (char *)lv_mem_alloc(len_text);
    snprintf(text, len_text, "[%d%s]", unread_count, new_msg);
    lv_label_set_text(label, text);
    lv_mem_free(text);
}

static void voice_msg_create_multi_unread_msgs(lv_obj_t * obj,
        uint8_t unread_count,
        lv_ll_t * contact_list,
        voice_msg_contact_t * contact)
{
    printf("%s: unread count %d\n", __FUNCTION__, unread_count);

    lv_voice_msg_incoming_ext_t * ext;
#if VOICE_MSG_ENABLE_ONE_UNREAD_MSG != 0
    if(NULL == obj) {
        lv_watch_activity_ext_t activity_ext;
        memset(&activity_ext, 0, sizeof(lv_watch_activity_ext_t));
        activity_ext.actId = ACT_ID_VOICE_MSG_INCOMING;
        activity_ext.create = NULL;
        activity_ext.prepare_destory = voice_msg_incoming_prepare_destory;
        lv_obj_t * activity_obj = lv_watch_creat_activity_obj(&activity_ext);
        LV_ASSERT_MEM(activity_obj);

        obj = lv_watch_obj_create(activity_obj);
        LV_ASSERT_MEM(obj);
        ext = lv_obj_allocate_ext_attr(obj, sizeof(lv_voice_msg_incoming_ext_t));
        LV_ASSERT_MEM(ext);
        ext->voice_playing = false;
        ext->update = false;
    } else {
        ext = lv_obj_get_ext_attr(obj);
        if(ext->voice_playing) {
            ext->voice_playing = false;
            voice_msg_play_end(ext->voice_size);
            Hal_Audio_Manage_End_Ind(AUDIO_CTRL_PRIORITY_5);
        }
        if(ext->voice_data) {
            lv_mem_free(ext->voice_data);
        } else if(ext->img_src) {
            lv_mem_free(ext->img_src->data);
            lv_mem_free(ext->img_src);
        }
        lv_obj_clean(obj);
        if(voice_msg_is_higher_prio_task_present(ACT_ID_ANYOBJ)) {
            ext->update = true;
        }
    }
    ext->voice_size = 0;
    ext->voice_data = NULL;
    ext->img_src = NULL;
#else
    lv_watch_activity_ext_t activity_ext;
    memset(&activity_ext, 0, sizeof(lv_watch_activity_ext_t));
    activity_ext.actId = ACT_ID_VOICE_MSG_INCOMING;
    activity_ext.create = NULL;
    activity_ext.prepare_destory = voice_msg_incoming_prepare_destory;
    lv_obj_t * activity_obj = lv_watch_creat_activity_obj(&activity_ext);
    LV_ASSERT_MEM(activity_obj);

    obj = lv_watch_obj_create(activity_obj);
    LV_ASSERT_MEM(obj);
    ext = lv_obj_allocate_ext_attr(obj, sizeof(lv_voice_msg_incoming_ext_t));
    LV_ASSERT_MEM(ext);
    ext->update = false;
#endif
    ext->contact_list = contact_list;
    ext->cur_contact = contact;

    lv_obj_t * cont = lv_cont_create(obj, NULL);
    lv_obj_set_size(cont, lv_obj_get_width(obj), lv_obj_get_height(obj));
    lv_obj_align(cont, obj, LV_ALIGN_CENTER, 0, 0);
    lv_obj_set_style_local_bg_color(cont,
                                    LV_CONT_PART_MAIN,
                                    LV_STATE_DEFAULT,
                                    LV_COLOR_MAKE(194, 252, 252));
    lv_watch_obj_add_element(cont);

    lv_obj_t * img_multi = lv_img_create(cont, NULL);
    lv_img_set_src(img_multi, ICON_MULTI_MSG);
    lv_obj_align(img_multi, cont, LV_ALIGN_IN_TOP_MID, 0, 20);

    lv_obj_t * label_multi = lv_label_create(cont, NULL);
    lv_obj_set_style_local_text_font(label_multi,
                                     LV_LABEL_PART_MAIN,
                                     LV_STATE_DEFAULT,
                                     LV_THEME_WATCH_NIGHT_FONT_SMALL);
    lv_obj_set_style_local_text_color(label_multi,
                                      LV_LABEL_PART_MAIN,
                                      LV_STATE_DEFAULT,
                                      LV_COLOR_MAKE(34, 34, 34));
    voice_msg_set_multi_label(label_multi, unread_count);
    lv_obj_align(label_multi, img_multi, LV_ALIGN_OUT_BOTTOM_MID, 0, 10);

    lv_obj_t * img_close = lv_img_create(cont, NULL);
    lv_img_set_src(img_close, ICON_CLOSE);
    lv_obj_set_click(img_close, true);
    lv_obj_set_event_cb(img_close, voice_msg_img_close_event_cb);
    lv_obj_set_adv_hittest(img_close, false);
    lv_watch_obj_add_element(img_close);

    lv_obj_t * img_back = lv_img_create(cont, NULL);
    lv_img_set_src(img_back, ICON_BACK);
    lv_obj_set_click(img_back, true);
    lv_obj_set_event_cb(img_back, voice_msg_img_back_event_cb);
    lv_obj_set_adv_hittest(img_back, false);
    lv_watch_obj_add_element(img_back);

    if(LV_BIDI_DIR_RTL == lv_obj_get_base_dir(cont)) {
        lv_obj_align(img_back, cont, LV_ALIGN_IN_BOTTOM_LEFT, 5, -8);
        lv_obj_align(img_close, cont, LV_ALIGN_IN_BOTTOM_RIGHT, -5, -8);
    } else {
        lv_obj_align(img_close, cont, LV_ALIGN_IN_BOTTOM_LEFT, 5, -8);
        lv_obj_align(img_back, cont, LV_ALIGN_IN_BOTTOM_RIGHT, -5, -8);
    }
}

static void voice_msg_update_multi_unread_msgs(lv_obj_t * obj, uint8_t unread_count)
{
    printf("%s: unread count %d\n", __FUNCTION__, unread_count);

    lv_obj_t * cont = lv_obj_get_child_back(obj, NULL);
    lv_obj_t * img_multi = lv_obj_get_child_back(cont, NULL);
    lv_obj_t * label_multi = lv_obj_get_child_back(cont, img_multi);
    voice_msg_set_multi_label(label_multi, unread_count);
    lv_obj_align(label_multi, img_multi, LV_ALIGN_OUT_BOTTOM_MID, 0, 10);
    lv_voice_msg_incoming_ext_t * ext = lv_obj_get_ext_attr(obj);
    LV_ASSERT_MEM(ext);
    if(voice_msg_is_higher_prio_task_present(ACT_ID_ANYOBJ)) {
        ext->update = true;
    }
}

static void voice_msg_sending_anim(lv_anim_t * a)
{
    lv_obj_t * img = a->var;
    uint8_t step = voice_msg_get_user_num(img);
    switch(step) {
        case 1:
            lv_img_set_src(img, ICON_SENDING2);
            voice_msg_set_user_num(img, 2);
            break;
        case 2:
            lv_img_set_src(img, ICON_SENDING3);
            voice_msg_set_user_num(img, 3);
            break;
        case 3:
            lv_img_set_src(img, ICON_SENDING1);
            voice_msg_set_user_num(img, 1);
            break;
        default:
            break;
    }
}

lv_obj_t * voice_msg_create_send(lv_obj_t * activity_obj)
{
    printf("%s\n", __FUNCTION__);

    if(NULL == activity_obj) {
        lv_watch_activity_ext_t activity_ext;
        memset(&activity_ext, 0, sizeof(lv_watch_activity_ext_t));
        activity_ext.actId = ACT_ID_VOICE_MSG_SEND;
        activity_ext.create = voice_msg_create_send;
        activity_ext.prepare_destory = voice_msg_common_prepare_destory;
        activity_obj = lv_watch_creat_activity_obj(&activity_ext);
        LV_ASSERT_MEM(activity_obj);
    }

    lv_obj_t * obj = lv_watch_obj_create(activity_obj);
    LV_ASSERT_MEM(obj);

    lv_obj_t * img_bkg = lv_img_create(obj, NULL);
    lv_img_set_src(img_bkg, ICON_SENDING_VOICE_BKG);
    lv_obj_align(img_bkg, obj, LV_ALIGN_CENTER, 0, 0);

    lv_obj_t * img_sending = lv_img_create(img_bkg, NULL);
    lv_img_set_src(img_sending, ICON_SENDING3);
    lv_obj_align(img_sending, img_bkg, LV_ALIGN_IN_BOTTOM_MID, 0, -20);
    voice_msg_set_user_num(img_sending, 3);

    lv_anim_t a = {};
    lv_anim_init(&a);
    lv_anim_set_var(&a, img_sending);
    lv_anim_set_start_cb(&a, (lv_anim_ready_cb_t)voice_msg_sending_anim);
    lv_anim_set_repeat_count(&a, LV_ANIM_REPEAT_INFINITE);
    lv_anim_set_repeat_delay(&a, 500);
    lv_anim_start(&a);

    return obj;
}

static void voice_msg_remove_list_btn(lv_obj_t * btn, bool final)
{
    voice_msg_type_t type = VOICE_MSG_GET_MSG_TYPE(voice_msg_get_user_num(btn));
    if((VOICE_MSG_TYPE_EXPRESSION == type) || (VOICE_MSG_TYPE_PHOTO == type)) {
        const void * src = lv_img_get_src(btn);
        if(LV_IMG_SRC_VARIABLE == lv_img_src_get_type(src)) {
            lv_img_dsc_t * dsc = (lv_img_dsc_t *)src;
            lv_mem_free(dsc->data);
            lv_mem_free(dsc);
        }
    } else if(VOICE_MSG_TYPE_VOICE == type) {
        lv_obj_t * len_label = lv_obj_get_child(btn, NULL);
        void * ptr = voice_msg_get_user_ptr(len_label);
        if(ptr) lv_mem_free(ptr);
    }
    if(final) {
        lv_obj_t * scrl = lv_obj_get_parent(btn);
        if(lv_obj_get_child(scrl, btn)) {
            /*move up the objs below btn*/
            lv_obj_t * child = lv_obj_get_child_back(scrl, btn);
            lv_coord_t last_y = lv_obj_get_y(btn);
            while(child) {
                lv_obj_set_y(child, last_y);
                last_y += lv_obj_get_height(child)
                    + lv_obj_get_style_pad_inner(scrl, LV_STATE_DEFAULT);
                if(VOICE_MSG_IS_UNREAD_FLAG(voice_msg_get_user_num(child))) {
                    /*next obj is unread flag*/
                    lv_obj_t * read_img = lv_obj_get_child_back(scrl, child);
                    lv_obj_align(read_img, child, LV_ALIGN_OUT_RIGHT_MID, 5, 0);
                    child = read_img;
                }
                child = lv_obj_get_child_back(scrl, child);
            }
        }
    }
    lv_obj_del(btn);
}

static void voice_msg_remove_top_msg(lv_obj_t * scrl)
{
    lv_obj_t * btn = lv_obj_get_child_back(scrl, NULL);
    lv_obj_t * btn_time = NULL;
    lv_obj_t * btn_name = NULL;
    lv_obj_t * btn_del = NULL;
    lv_obj_t * btn_cur;
    while(btn) {
        btn_cur = btn;
        btn = lv_obj_get_child_back(scrl, btn);

        uint32_t free_num = voice_msg_get_user_num(btn_cur);
        voice_msg_type_t type = VOICE_MSG_GET_MSG_TYPE(free_num);
        if(VOICE_MSG_TYPE_TIME == type) {
            if(NULL == btn_time) {
                btn_time = btn_cur;
            } else {
                voice_msg_remove_list_btn(btn_time, false);
            }
        } else if(VOICE_MSG_TYPE_NAME == type) {
            if(NULL == btn_name) {
                btn_name = btn_cur;
            } else {
                voice_msg_remove_list_btn(btn_name, false);
                btn_name = NULL;
            }
        } else if(VOICE_MSG_TYPE_UNREAD_FLAG == type) {
            voice_msg_remove_list_btn(btn_cur, false);
        } else {
            if(NULL == btn_del) {
                btn_del = btn_cur;
            } else {
                if((VOICE_MSG_TYPE_VOICE == type)
                        || (VOICE_MSG_TYPE_EXPRESSION == type)
                        || (VOICE_MSG_TYPE_PHOTO == type)) {
                    if((NULL != btn_name) && (0 == VOICE_MSG_IS_MSG_FROM_SERVICE(free_num))) {
                        /*voice or expression or photo message from ui, not name above the message */
                        voice_msg_remove_list_btn(btn_name, false);
                    }
                }
                voice_msg_remove_list_btn(btn_del, true);
                break;
            }
        }
    }
}

static void voice_msg_play_tone_and_wakeup(uint8_t direction)
{
    if((WATCH_VOICE_MSG_FROM_OTHER_DEVICE == direction)
            && (AUDIO_CTRL_PRIORITY_5 == Hal_Audio_Manage_Start_Req(AUDIO_CTRL_PRIORITY_5,voice_msg_audio_ctrl_callback))
            && (NULL == lv_watch_get_activity_obj(ACT_ID_VOICE_MSG_SPEAK))) {
        Voice_Msg_Play_Onetime(AUDIO_MSG, 0, query_current_volume(), NULL, NULL);
        voice_msg_lcd_wakeup();
    }
}

static void voice_msg_add_new_msg_to_chat(app_adaptor_voice_msg_t * msgs)
{
    lv_voice_msg_chat_ext_t * ext = voice_msg_get_ext(ACT_ID_VOICE_MSG_CHAT);
    if(NULL == ext) return;
    LV_ASSERT_MEM(ext->img_touch);

    lv_obj_t * scrl;
    lv_voice_msg_list_ext_t * list_ext;
    bool at_bottom = false;
    lv_obj_t * cont = lv_obj_get_parent(ext->cont_title);
    lv_coord_t scrl_init_y;
    lv_coord_t scrl_scrn_height;
    if(NULL == ext->list) {
        lv_obj_del(ext->label_no_msg);
        ext->label_no_msg = NULL;

        /*no message, create list for adding new message*/
        lv_coord_t list_height = lv_obj_get_height(cont) - lv_obj_get_height(ext->cont_title) - 4;
        ext->list = voice_msg_list_create(cont, lv_obj_get_width(cont), list_height);
        lv_obj_align(ext->list, ext->cont_title, LV_ALIGN_OUT_BOTTOM_LEFT, 0, 0);
        list_ext = lv_obj_get_ext_attr(ext->list);
        if(NULL == list_ext) return;
        scrl_init_y = lv_obj_get_style_pad_top(ext->list, LV_LIST_PART_BG);
        scrl_scrn_height = list_height - scrl_init_y * 2;
        scrl = lv_page_get_scrl(ext->list);
    } else {
        list_ext = lv_obj_get_ext_attr(ext->list);
        if(NULL == list_ext) return;

        scrl = lv_page_get_scrl(ext->list);
        lv_coord_t scrl_h = lv_obj_get_height(scrl);
        lv_coord_t scrl_y = lv_obj_get_y(scrl);
        scrl_init_y = lv_obj_get_style_pad_top(ext->list, LV_LIST_PART_BG);
        lv_coord_t scrl_top_h = scrl_init_y - scrl_y; /*part of scrl at the top of list*/
        scrl_scrn_height = lv_obj_get_height(ext->list) - scrl_init_y * 2;
        if((scrl_h - scrl_top_h) <= scrl_scrn_height) {
            /*at the bottom of chat window*/
            at_bottom = true;
        }

        /*delete emo and touch img*/
        if(ext->img_emo) {
            lv_obj_set_parent(ext->img_emo, cont);
        }
        lv_obj_t * cont_emo_touch = lv_obj_get_parent(ext->img_touch);
        lv_obj_set_parent(ext->img_touch, cont);
        lv_obj_del(cont_emo_touch);

        printf("%s: msg count %d\n", __FUNCTION__, ext->msg_count);
        if(VOICE_MSG_MAX_CHAT_MSG_NUM == ext->msg_count) {
            /*reach maximum number of messages,  delete the oldest message*/
            voice_msg_remove_top_msg(scrl);
        } else {
            ext->msg_count++;
        }
    }

    /* add new voice message to message list */
    lv_obj_t * btn_emo_touch = voice_msg_display_msg_to_chat(ext->list, msgs, &ext->last_msg_time);

    lv_coord_t scrl_height = lv_obj_get_height(scrl);
    if(scrl_scrn_height > scrl_height) {
        /*set emo and touch img to bottom of chat window*/
        lv_coord_t btn_h = lv_obj_get_height(btn_emo_touch);
        lv_coord_t new_btn_y = scrl_scrn_height - btn_h;
        lv_obj_set_y(btn_emo_touch, new_btn_y);
        voice_msg_play_tone_and_wakeup(msgs->msg->direction);
    } else {
        if(false == at_bottom) {
            if(list_ext->voice_playing_anim.var) {
                /*voice is playing*/
                Hal_Vibrator_Play_Onetime(NULL, 0);
            } else {
                lv_page_focus(ext->list, btn_emo_touch, false);
                voice_msg_play_tone_and_wakeup(msgs->msg->direction);
            }
        } else {
            /*at the bottom of chat window*/
            lv_page_focus(ext->list, btn_emo_touch, false);

            if(NULL == list_ext->voice_playing_anim.var) {
                /*not playing voice*/
                voice_msg_play_tone_and_wakeup(msgs->msg->direction);
            }
        }
    }
}

static bool voice_msg_send_voice(lv_voice_msg_chat_ext_t * ext)
{
    if(NULL == ext) return false;
    if(NULL == ext->buffer) return false;

    uint32_t size = voice_msg_record_buffer_stop_req();
    printf("%s: buffer size %d\n", __FUNCTION__, size);
    Hal_Audio_Manage_End_Ind(AUDIO_CTRL_PRIORITY_5);
    if(VOICE_MSG_AMR_HEAD_SIZE >= size) {
        lv_mem_free(ext->buffer);
        ext->buffer = NULL;
        return false;
    }

    /*send voice to adaptor*/
    app_adaptor_voice_msg_t * msgs = (app_adaptor_voice_msg_t *)lv_mem_alloc(sizeof(app_adaptor_voice_msg_t));
    memset(msgs, 0, sizeof(app_adaptor_voice_msg_t));
    app_adaptor_voice_msg_chat_id_t id;
    voice_msg_fill_chat_id(&id, ext->cur_contact);
    msgs->chat_type = id.chat_type;
    msgs->name = id.name;
    msgs->number = id.number;
    msgs->count = 1;
    msgs->msg = (app_adaptor_voice_msg_info_t *)lv_mem_alloc(sizeof(app_adaptor_voice_msg_info_t));
    memset(msgs->msg, 0, sizeof(app_adaptor_voice_msg_info_t));
    msgs->msg->direction = WATCH_VOICE_MSG_FROM_UI;
    hal_rtc_t time;
    Hal_Rtc_Gettime(&time);
    msgs->msg->time.year = time.tm_year;
    msgs->msg->time.month = time.tm_mon;
    msgs->msg->time.day = time.tm_mday;
    msgs->msg->time.hour = time.tm_hour;
    msgs->msg->time.min = time.tm_min;
    msgs->msg->type = WATCH_VOICE_MSG_TYPE_VOICE;
    msgs->msg->data_type = WATCH_VOICE_DATA_TYPE_BUFFER;
    msgs->msg->content.voice.voice_len = size;
    msgs->msg->content.voice.file = lv_mem_alloc(size);
    memcpy(msgs->msg->content.voice.file, ext->buffer, size);

    char * path = app_adaptor_voice_msg_send_req(msgs);
    if(NULL == path) {
        printf("%s: buffer\n", __FUNCTION__);
    } else {
        printf("%s: file path %s\n", __FUNCTION__, path);
    }

    /*update chat*/
    msgs = (app_adaptor_voice_msg_t *)lv_mem_alloc(sizeof(app_adaptor_voice_msg_t));
    memset(msgs, 0, sizeof(app_adaptor_voice_msg_t));
    voice_msg_fill_chat_id(&id, ext->cur_contact);
    msgs->chat_type = id.chat_type;
    msgs->name = id.name;
    msgs->number = id.number;
    msgs->count = 1;
    msgs->msg = (app_adaptor_voice_msg_info_t *)lv_mem_alloc(sizeof(app_adaptor_voice_msg_info_t));
    memset(msgs->msg, 0, sizeof(app_adaptor_voice_msg_info_t));
    msgs->msg->direction = WATCH_VOICE_MSG_FROM_UI;
    msgs->msg->time.year = time.tm_year;
    msgs->msg->time.month = time.tm_mon;
    msgs->msg->time.day = time.tm_mday;
    msgs->msg->time.hour = time.tm_hour;
    msgs->msg->time.min = time.tm_min;
    msgs->msg->type = WATCH_VOICE_MSG_TYPE_VOICE;
    if(path) {
        /*buffer has been written into file by SDK*/
        msgs->msg->data_type = WATCH_VOICE_DATA_TYPE_FILE;
        msgs->msg->content.voice.file = (uint8_t *)path;
        msgs->msg->content.voice.voice_len = 0;
        lv_mem_free(ext->buffer);
    } else {
        msgs->msg->data_type = WATCH_VOICE_DATA_TYPE_BUFFER;
        msgs->msg->content.voice.voice_len = size;
        msgs->msg->content.voice.file = ext->buffer;
    }
    ext->buffer = NULL;
    voice_msg_add_new_msg_to_chat(msgs);
    voice_msg_free_msgs(msgs);

#if VOICE_MSG_SORT_CONTACTS_BY_MSG_TIME != 0
    voice_msg_contact_t * head = _lv_ll_get_head(ext->contact_list);
    if(head != ext->cur_contact) {
        lv_ll_move_before(ext->contact_list, ext->cur_contact, head);
        ext->contact_list_update = true;
    }
#endif

    return true;
}

static void voice_msg_send_end_anim(lv_anim_t * a)
{
    lv_obj_t * activity_obj = ((lv_anim_t *)a)->var;
    lv_watch_activity_ext_t * act_ext = lv_obj_get_ext_attr(activity_obj);
    if(NULL == act_ext) return;

    if(act_ext->prepare_destory) {
        act_ext->prepare_destory(activity_obj);
    }
    lv_obj_del(activity_obj);
}

static void voice_msg_expression_event_cb(lv_obj_t * img, lv_event_t e)
{
    lv_voice_msg_expression_ext_t * exp_ext = voice_msg_get_ext(ACT_ID_VOICE_MSG_EXPRESSION);
    if(NULL == exp_ext) return;

    if(LV_EVENT_PRESSED == e) {
        exp_ext->pressing = true;
    } else if((true == exp_ext->pressing) && (LV_EVENT_CLICKED == e)) {
        char * src = (char *)lv_img_get_src(img);
        if(NULL == src) return;

        lv_voice_msg_chat_ext_t * ext = voice_msg_get_ext(ACT_ID_VOICE_MSG_CHAT);
        if(NULL == ext) return;

        /*send expression to adaptor*/
        app_adaptor_voice_msg_t * msgs = (app_adaptor_voice_msg_t *)lv_mem_alloc(sizeof(app_adaptor_voice_msg_t));
        memset(msgs, 0, sizeof(app_adaptor_voice_msg_t));
        app_adaptor_voice_msg_chat_id_t id;
        voice_msg_fill_chat_id(&id, ext->cur_contact);
        msgs->chat_type = id.chat_type;
        msgs->name = id.name;
        msgs->number = id.number;
        msgs->count = 1;
        msgs->msg = (app_adaptor_voice_msg_info_t *)lv_mem_alloc(sizeof(app_adaptor_voice_msg_info_t));
        memset(msgs->msg, 0, sizeof(app_adaptor_voice_msg_info_t));
        msgs->msg->direction = WATCH_VOICE_MSG_FROM_UI;
        hal_rtc_t time;
        Hal_Rtc_Gettime(&time);
        msgs->msg->time.year = time.tm_year;
        msgs->msg->time.month = time.tm_mon;
        msgs->msg->time.day = time.tm_mday;
        msgs->msg->time.hour = time.tm_hour;
        msgs->msg->time.min = time.tm_min;
        msgs->msg->type = WATCH_VOICE_MSG_TYPE_EXPRESSION;
        msgs->msg->data_type = WATCH_VOICE_DATA_TYPE_FILE;
        msgs->msg->content.img.data_size = strlen(src);
        printf("%s: src %s\n", __FUNCTION__, src);
        msgs->msg->content.img.data = lv_mem_alloc(msgs->msg->content.img.data_size + 1);
        memcpy(msgs->msg->content.img.data, src, msgs->msg->content.img.data_size + 1);

        /*update chat*/
        voice_msg_add_new_msg_to_chat(msgs);

        app_adaptor_voice_msg_send_req(msgs);

        voice_msg_del_top_interface();

        voice_msg_create_send(NULL);

#if VOICE_MSG_SORT_CONTACTS_BY_MSG_TIME != 0
        voice_msg_contact_t * head = _lv_ll_get_head(ext->contact_list);
        if(head != ext->cur_contact) {
            lv_ll_move_before(ext->contact_list, ext->cur_contact, head);
            ext->contact_list_update = true;
        }
#endif
    }
}

static lv_res_t voice_msg_expression_page_scrl_signal(lv_obj_t * page_scrl, lv_signal_t sign, void * param)
{
    lv_voice_msg_expression_ext_t * ext = voice_msg_get_ext(ACT_ID_VOICE_MSG_EXPRESSION);
    if(NULL == ext) return LV_RES_INV;

    lv_res_t res = ext->ancient_page_scrl_signal(page_scrl, sign, param);
    if(res != LV_RES_OK) return res;

    if(LV_SIGNAL_COORD_CHG == sign) {
        if(true == ext->pressing) {
            ext->pressing = false;
        }
    }

    return LV_RES_OK;
}

static lv_obj_t  * voice_msg_create_expression(lv_obj_t * activity_obj)
{
    printf("%s\n", __FUNCTION__);

    if(activity_obj == NULL) {
        lv_watch_activity_ext_t activity_ext;
        memset(&activity_ext, 0, sizeof(lv_watch_activity_ext_t));
        activity_ext.actId = ACT_ID_VOICE_MSG_EXPRESSION;
        activity_ext.create = voice_msg_create_expression;
        activity_ext.prepare_destory = voice_msg_common_prepare_destory;
        activity_obj = lv_watch_creat_activity_obj(&activity_ext);
        LV_ASSERT_MEM(activity_obj);
    }

    lv_obj_t * obj = lv_watch_obj_create(activity_obj);
    LV_ASSERT_MEM(obj);
    lv_voice_msg_expression_ext_t * ext = lv_obj_allocate_ext_attr(obj, sizeof(lv_voice_msg_expression_ext_t));
    LV_ASSERT_MEM(ext);
    ext->pressing = false;

    lv_watch_obj_set_anim_mode(obj, LV_WATCH_ANIM_VER_UP_HIDE);

    lv_obj_t * page = lv_page_create(obj, NULL);
    lv_obj_set_size(page, lv_obj_get_width(obj), lv_obj_get_height(obj));
    lv_obj_add_style(page, LV_PAGE_PART_BG, &lv_style_transp_tight);
    lv_obj_add_style(page, LV_PAGE_PART_SCROLLABLE, &lv_style_transp_tight);
    lv_obj_add_style(page, LV_PAGE_PART_EDGE_FLASH, &lv_watch_cont_opa1);
    lv_page_set_scrollbar_mode(page, LV_SCROLLBAR_MODE_OFF);
    lv_page_set_scrollable_fit2(page, LV_FIT_TIGHT, LV_FIT_NONE);
    lv_page_set_edge_flash(page, true);
    lv_page_set_scrl_height(page, lv_obj_get_height(obj));
    lv_obj_t * scrl = lv_page_get_scrl(page);
    ext->ancient_page_scrl_signal = lv_obj_get_signal_cb(scrl);
    lv_obj_set_signal_cb(scrl, voice_msg_expression_page_scrl_signal);
    lv_watch_obj_add_element(page);

    /*image "icon_expression_test" only for test, replace them with actual expression images */
    char * expression[] = {ICON_EXPRESSION_TEST,
                           ICON_EXPRESSION_TEST,
                           ICON_EXPRESSION_TEST,
                           ICON_EXPRESSION_TEST,
                           ICON_EXPRESSION_TEST,
                           ICON_EXPRESSION_TEST
    };
    uint8_t exp_count = sizeof(expression) / sizeof(lv_img_dsc_t *);
    printf("%s: expression count %d\n", __FUNCTION__, exp_count);

    lv_bidi_dir_t dir = lv_obj_get_base_dir(obj);

    lv_obj_t * img = lv_img_create(page, NULL);
    lv_img_set_src(img, expression[0]);
    if(LV_BIDI_DIR_RTL == dir) {
        lv_obj_align(img, page, LV_ALIGN_IN_TOP_RIGHT, 0, 0);
    } else {
        lv_obj_align(img, page, LV_ALIGN_IN_TOP_LEFT, 0, 0);
    }
    voice_msg_set_user_ptr(img, (void *)expression[0]);
    lv_obj_add_protect(img, LV_PROTECT_PRESS_LOST);
    lv_obj_set_click(img, true);
    lv_page_glue_obj(img, true);
    lv_obj_set_event_cb(img, voice_msg_expression_event_cb);
    lv_obj_set_adv_hittest(img, false);
    lv_watch_obj_add_element(img);
    for(uint8_t i = 1; i < exp_count; i++) {
        lv_obj_t * new_img = lv_img_create(page, NULL);
        lv_img_set_src(new_img, expression[i]);
        if(0 != (i % 2)) {
            if(LV_BIDI_DIR_RTL == dir) {
                lv_obj_align(new_img, img, LV_ALIGN_OUT_BOTTOM_RIGHT, 0, 0);
            } else {
                lv_obj_align(new_img, img, LV_ALIGN_OUT_BOTTOM_LEFT, 0, 0);
            }
        } else {
            if(LV_BIDI_DIR_RTL == dir) {
                lv_obj_align(new_img, img, LV_ALIGN_OUT_LEFT_TOP, 0, 0);
            } else {
                lv_obj_align(new_img, img, LV_ALIGN_OUT_RIGHT_TOP, 0, 0);
            }
            img = new_img;
        }
        lv_obj_set_click(new_img, true);
        lv_page_glue_obj(new_img, true);
        lv_obj_set_event_cb(new_img, voice_msg_expression_event_cb);
        lv_watch_obj_add_element(new_img);
    }

    if(LV_BIDI_DIR_RTL == dir) {
        lv_obj_set_x(scrl, -lv_obj_get_width(scrl));
    }

    return obj;
}

static void voice_msg_enlarged_photo_event_cb(lv_obj_t * obj, lv_event_t e)
{
    if(LV_EVENT_CLICKED == e) {
        printf("%s\n", __FUNCTION__);

        lv_obj_t * activity_obj = lv_watch_get_activity_obj(ACT_ID_VOICE_MSG_PHOTO);
        LV_ASSERT_MEM(activity_obj);
        lv_watch_activity_ext_t * act_ext = lv_obj_get_ext_attr(activity_obj);
        LV_ASSERT_MEM(act_ext);

        if(act_ext->prepare_destory) {
            act_ext->prepare_destory(activity_obj);
        }
        lv_obj_del(activity_obj);
    }
}

static void voice_msg_enlarged_photo_prepare_destory(lv_obj_t * activity_obj)
{
    printf("%s\n", __FUNCTION__);
    lv_watch_png_cache_all_free();
}

static void voice_msg_create_enlarged_photo(void * photo)
{
    if(NULL == photo) {
        return;
    }

    lv_watch_activity_ext_t activity_ext;
    memset(&activity_ext, 0, sizeof(lv_watch_activity_ext_t));
    activity_ext.actId = ACT_ID_VOICE_MSG_PHOTO;
    activity_ext.create = NULL;
    activity_ext.prepare_destory = voice_msg_enlarged_photo_prepare_destory;
    lv_obj_t * activity_obj = lv_watch_creat_activity_obj(&activity_ext);
    LV_ASSERT_MEM(activity_obj);

    lv_obj_t * obj = lv_watch_obj_create(activity_obj);
    LV_ASSERT_MEM(obj);

    lv_obj_t * cont = lv_cont_create(obj, NULL);
    lv_obj_set_style_local_bg_color(cont,
                                    LV_CONT_PART_MAIN,
                                    LV_STATE_DEFAULT,
                                    LV_COLOR_BLACK);
    lv_coord_t width_obj = lv_obj_get_width(obj);
    lv_coord_t height_obj = lv_obj_get_height(obj);
    lv_obj_set_size(cont, width_obj, height_obj);
    lv_watch_obj_add_element(cont);

    lv_obj_t * img = lv_img_create(cont, NULL);
    lv_img_set_src(img, photo);
    lv_img_set_auto_size(img, true);
    lv_coord_t width_img;
    lv_coord_t height_img;
    if(LV_IMG_SRC_FILE == lv_img_src_get_type(photo)) {
        lv_img_header_t header;
        lv_img_decoder_get_info(photo, &header);
        width_img = header.w;
        height_img = header.h;
    } else {
        width_img = ((lv_img_dsc_t *)photo)->header.w;
        height_img = ((lv_img_dsc_t *)photo)->header.h;
    }
    uint8_t multiple = ((width_obj / width_img) > (height_obj / height_img))
        ? (height_obj / height_img) : (width_obj / width_img);
    printf("%s: obj w %d, h %d, multiple %d, w %d, h %d\n",
           __FUNCTION__, width_obj, height_obj, multiple, width_img, height_img);
    lv_img_set_zoom(img, 256 * multiple);
    lv_obj_align(img, obj, LV_ALIGN_CENTER, 0, 0);
    lv_obj_set_click(img, true);
    lv_obj_set_event_cb(img, voice_msg_enlarged_photo_event_cb);
    lv_watch_obj_add_element(img);
}

#endif /*USE_LV_WATCH_VOICE_MSG*/
